• 🏆 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!

Posts moved from FP's tutorial thingy

Status
Not open for further replies.
Level 10
Joined
Aug 19, 2008
Messages
491
I just realized you should add a section on how to use the JASS NewGen Pack, and add the pack itself to Attachments instead of linking it.
Took me a while to figure out how to use it, and why I couldn't use the normal one, and I'm quite good at those things.
It would spare a lot of time answering questions like "Why can't it work?! :cry:" and similar.
 
I just realized you should add a section on how to use the JASS NewGen Pack, and add the pack itself to Attachments instead of linking it.
Took me a while to figure out how to use it, and why I couldn't use the normal one, and I'm quite good at those things.
It would spare a lot of time answering questions like "Why can't it work?! " and similar.
Although I don't have that section, I think I explain that in the tutorial. I say that you must first save the map, and then run it. I think it is in the scope section you know =P
 
Level 10
Joined
Aug 19, 2008
Messages
491
Although I don't have that section, I think I explain that in the tutorial. I say that you must first save the map, and then run it. I think it is in the scope section you know =P

I mean like really, totally and utterly explaining that vJass is only avilable in NewGen WE, if you use it in normal Editor it will not work.
Or some similar statement :grin:
And then explain how it works, like you did :wink:

These are just my own preferances, I'm not sure if anyone else will miss it.
The worst that can happen is that some of your readers just stop reading the whole thing because they can't make your final code work when copy/paste it to the normal WE, and that would just suck :thumbs_down:


EDIT:
Oh and while I'm at it I might aswell ak you about this line:
JASS:
private function Targets takes unit target returns boolean
    return (GetWidgetLife( target ) > 0.405) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false) and ( IsUnitType( target, UNIT_TYPE_MECHANICAL ) == false)
endfunction
How did 'target' come into the picture? I don't see it as a type, or as a variable we've declared :confused:
Is this something new from vJass?
 
I mean like really, totally and utterly explaining that vJass is only avilable in NewGen WE, if you use it in normal Editor it will not work.
This is quite wrong. You can use vJass as long as you have JassHelper. You are not forced to have JNGP, but downloading it is surely a good idea.
I will see what I can do about this.

How did 'target' come into the picture? I don't see it as a type, or as a variable we've declared
Is this something new from vJass?
No, I am passing an argument to that function just like I pass level as an argument for Damage. Read the code carefully, if you still don't understand it, tell me the section where you are going and let me help you.
 
Level 10
Joined
Aug 19, 2008
Messages
491
This is quite wrong. You can use vJass as long as you have JassHelper. You are not forced to have JNGP, but downloading it is surely a good idea.
I will see what I can do about this.
Oh I see. I thought that it was NewGen WE which did the job :xxd:

No, I am passing an argument to that function just like I pass level as an argument for Damage. Read the code carefully, if you still don't understand it, tell me the section where you are going and let me help you.

Yeah I found that line...
JASS:
private function Targets takes unit target returns boolean
Here it 'takes unit target'.
Ok so my next question will be how that works? From where does it take 'target'?
(This is where I'm stuck actualy)

Another question is Why do we wanna make a copy out of the group 'all'?
Why can't we just use the group 'all' instead of a copied one?
 
Level 10
Joined
Aug 19, 2008
Messages
491
In order to help you I need to know where you are in the tutorial. Like, I can't guess =P
Just tell the section and the paste the current code you are seeing.

I've actualy read the whole tutorial now :thumbs_up: and I understand almost everything.
Except for some things, and they're all located in the middle of the first page of this thread.

First Question
In this line it says it 'takes unit target'. From where does it take 'target'? And it returns boolean?
JASS:
private function Targets takes unit target returns boolean
    //the units the spell will affect
        return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
    endfunction
[Located around the middle of the first page of this thread]

Second Question
Although it isn't really neccessary I wanna know how this function works.
JASS:
function CopyGroup takes group g returns group
        set bj_groupAddGroupDest = CreateGroup()
        call ForGroup(g, function GroupAddGroupEnum)
        return bj_groupAddGroupDest
    endfunction

This is how far I've come to understanding it.
1st line: It takes group g from another function located somwhere, and will return another group
2nd line: creates a group and sets it to a variable
3rd line: calls a function which I can't find :confused:
4th line: returns the previously created group, which is somehow copied
5th line: ends the function
[Located right below where my first question is]

Third Question
I don't know why we wanna make a copy out of the 'all' group. Tell me!
[Located at the same spot as 2nd question]
 
First Question:
JASS:
//counting the units
        call GroupEnumUnitsInRange(all, spellX, spellY, Range(level), b)
In function Actions, you see we have this. Now, this line puts all unit that obey to boolexpr "b" inside group "all". But what is boolexpr "b" ?

JASS:
 globals
        private group all
        private group copy
        private boolexpr b
    endglobals
Moving up we see it is a global variable that we always use. However if we enter the Init function we also see this:
JASS:
//setting globals
        set all = CreateGroup()
        set copy = CreateGroup()
        set b = Condition(function Pick)
it seems like our boolexpr "b" is a Condition. But not any Condition, it is Condition that calls a function called "Pick". So let's see what Pick function is:
JASS:
 private function Pick takes nothing returns boolean
        return Targets(GetFilterUnit())
    endfunction
Mm now if we look closer, we see that function "Pick" calls yet another function. It call function "Targets" and gives it an argument. It gives to function Targets the argument "GetFilterUnit()", which is the unit being evaluated.

Let's see function "Targets":
JASS:
private function Targets takes unit target returns boolean
    //the units the spell will affect
        return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
    endfunction
So, targets receives a unit as a parameter. It is now clear (I hope) that the unit function "Targets" receives is "GetFilterUnit()" from function "Pick". Now you may ask, "but why are we calling function Targets? We could do all the job with function Pick". That is correct, however, by calling the function "Targets" you are making your spell easier for the user to change. It is easier for a user to know what a unit "target" means than knowing what "GetFilterUnit()" means, and many people don't know. The spell may be less efficient, but it is easier to change for the user.

So, now let's make a quick summary: when we enum units in group "all" according to boolexpr "b", this means that we pick all units in the area, and then we "send" the units to function "Pick". Function "Pick" sends these unit to function "Targets" and so we actually make our selection in function "Targets". Note that a boolexpr must always return a boolean. Because function "Targets" returns a boolean value, this means that function "Picks" (that calls function "Targets") also returns the same boolean value, and so boolexpr "b" returns a boolean answer, as it is supposed to do.

I hope you understand question 1, I will answer question 2 later.
 
Level 10
Joined
Aug 19, 2008
Messages
491
So I tried to make a spell with this tutorial in mind (it's just an exercise).
This is what it looks like (classic Mass Sleep spell)
JASS:
//***************************************************************************
//***** Mass Sleep
//***** Sleeps all units in an area.
//*****
//***** Area = 100 + 100 * level
//***** Duration = Change values in 'A001'
//***************************************************************************

scope MassSleep initializer Init
//***************************************************************************
//******************** SETUP ************************************************
//***************************************************************************
    
    globals 
        private constant integer MAIN_SPELL_ID = 'A000'
        private constant integer DUMMY_SPELL_ID = 'A001'
        private constant integer DUMMY_ID = 'h000'
    endglobals
    
    private function Range takes integer ab_level returns real
        return 100 + level * 100
    endfunction
    
    private function Targets takes unit target returns boolean
        return ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MECHANICAL ) == false )
    endfunction
    
//***************************************************************************
//****************** SETUP END **********************************************
//***************************************************************************
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == MAIN_SPELL_ID
    endfunction
//===========================================================================
    private function Pick takes nothing returns boolean
        return Targets( GetFilterUnit() )
    endfunction
//===========================================================================
    private function Actions takes nothing returns nothing
        local location spellLoc = GetSpellTargetLoc()
        local real spellX = GetLocationX( spellLoc )
        local real spellY = GetLocationX( spellLoc )
        local unit caster = GetTriggerUnit()
        local integer ab_level = GetUnitAbilityLevel( caster, SPELL_ID )
        local unit temp
        local unit dummy
        local location unitLoc
        call GroupEnumUnitsInRange( group, spellX, spellY, Range( level ), b )
        
        loop
            set temp = FirstOfGroup( group )
            set unitLoc = GetUnitLoc( temp )
            exitwhen ( temp == null )
            call GroupRemoveUnit( group, temp)
            if IsUnitEnemy( temp, GetOwningPlayer( caster ) ) then
                set dummy = CreateNUnitsAtLoc( 1, DUMMY_ID, GetOwningPlayer( caster ), unitLoc, 0.00 )
                call UnitAddAbility( dummy, DUMMY_SPELL_ID )
                call IssueTargetOrderBJ( dummy, "sleep", temp )
                call UnitApplyTimedLifeBJ( 1.50, 'BTLF', dummy ) 
            endif
        endloop
        
        call RemoveLocation( spellLoc )
        call RemoveLocation( unitLoc )
        set spellLoc = null
        set unitLoc = null
        set caster = null
        
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger MassSleepTrg = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( MassSleepTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( MassSleepTrg, Condition( function Conditions ) )
        call TriggerAddAction( MassSleepTrg, function Actions )
        
        set group = CreateGroup()
        set b = Condition( function Pick )
        
        set bj_lastCreatedUnit = CreateUnit( Player( PLAYER_NEUTRAL_PASSIVE ), DUMMY_ID, 0, 0, 0 )
        call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID )
        call KillUnit( bj_lastCreatedUnit)
endscope




Now, when I save the map it says "Line 123: Syntax Error" and shows this line:
JASS:
function InitCustomTriggers takes nothing returns nothing

What am I doing wrong?
Here's the map btw: View attachment MassSleep vJass.w3x
I made it in NewGen WE.
 
JASS:
call GroupEnumUnitsInRange( group, spellX, spellY, Range( level ), b )
Do not despair, it is a very simple error. You are using "group" to name a group and you cannot do that. You must:
1 - give the variable a name
2- create the variable

Same goes for boolexpr "b". You didn't create it. You are missing a "globals" block after the SETUPEND part.

Allow me:
JASS:
//***************************************************************************
//****************** SETUP END **********************************************
//***************************************************************************
globals
    private group victims //group g. It is a good idea to give nice names to variables
    private boolexpr chooseVics
endglobals

private function Actions takes nothing returns nothing
    //bla bla bla lots of stuff
    call GroupEnumUnitsInRange( victims, spellX, spellY, Range( level ), chooseVics )
endfunction

private function Init takes nothing returns nothing
    //stuff you made
    set victims = CreateGroup()
    set chooseVics = Condition( function Pick )

    //more stuff
endfunction
Also, you are missing "enfunction" in the function called "Init".
I think this is all. You still need me to explain that "question 2" you made? Did you understand my answer to question 1?

Also, in your loop:
JASS:
if IsUnitEnemy( temp, GetOwningPlayer( caster ) ) then
This line is not necessary. You can place this inside function Targets. How?
See my small modifications:
JASS:
//see my Targets modified function ?? This solution is a lot better. This way you give more freedom to the user and the spell is still MUI !
private function Targets takes unit caster, unit target returns boolean
        return ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MECHANICAL ) == false ) and IsUnitEnemy( target, GetOwningPlayer( caster ) )
    endfunction

//***************************************************************************
//****************** SETUP END **********************************************
//***************************************************************************
globals
    private group victims //group g. It is a good idea to give nice names to variables
    private boolexpr chooseVics
    private unit tmpCaster //a variable for a temporary caster
endglobals

private function Pick takes nothing returns boolean
        return Targets( tmpCaster, GetFilterUnit() )
endfunction

private function Actions takes nothing returns nothing
    //bla bla bla lots of stuff
    set tmpCaster = caster
    call GroupEnumUnitsInRange( victims, spellX, spellY, Range( level ), chooseVics )
endfunction
 
Level 10
Joined
Aug 19, 2008
Messages
491
I tried to fix it and things just got even more wierd :S

Here is the present code, I tried to fix stuff but apparently I failed.
JASS:
//***************************************************************************
//***** Mass Sleep
//***** Sleeps all units in an area.
//*****
//***** Area = 100 + 100 * level
//***** Duration = Change values in 'A001'
//***************************************************************************

scope MassSleep initializer Init
//***************************************************************************
//******************** SETUP ************************************************
//***************************************************************************
    
    globals 
        private constant integer MAIN_SPELL_ID = 'A000'
        private constant integer DUMMY_SPELL_ID = 'A001'
        private constant integer DUMMY_ID = 'h000'
    endglobals
    
    private function Range takes integer level returns real
        return I2R( 100 + level * 100 )
    endfunction
    
    private function Targets takes unit target returns boolean
        return ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false ) and IsUnitEnemy( target, GetOwningPlayer( GetTriggerUnit() ) )
    endfunction
    
//***************************************************************************
//****************** SETUP END **********************************************
//***************************************************************************

    globals
        private group victims
        private boolexpr chooseVictims
        private unit target
    endglobals

    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == MAIN_SPELL_ID
    endfunction
//===========================================================================
    private function Pick takes nothing returns boolean
        return Targets( target, GetFilterUnits() )
    endfunction
//===========================================================================
    private function Actions takes nothing returns nothing
        local location spellLoc = GetSpellTargetLoc()
        local real spellX = GetLocationX( spellLoc )
        local real spellY = GetLocationX( spellLoc )
        local integer level = GetUnitAbilityLevel( tempCaster, MAIN_SPELL_ID )
        local unit temp
        local unit dummy
        local location unitLoc
        set tempCaster = GetTriggerUnit()
        call GroupEnumUnitsInRange( victims, spellX, spellY, Range( level ), chooseVictims )
        
        loop
            set temp = FirstOfGroup( g )
            set unitLoc = GetUnitLoc( temp )
            exitwhen ( temp == null )
            call GroupRemoveUnit( group, temp)
            if IsUnitEnemy( temp, GetOwningPlayer( caster ) ) then
                set dummy = CreateNUnitsAtLoc( 1, DUMMY_ID, GetOwningPlayer( caster ), unitLoc, 0.00 )
                call UnitAddAbility( dummy, DUMMY_SPELL_ID )
                call IssueTargetOrderBJ( dummy, "sleep", temp )
                call UnitApplyTimedLifeBJ( 1.50, 'BTLF', dummy ) 
            endif
        endloop
        
        call RemoveLocation( spellLoc )
        call RemoveLocation( unitLoc )
        set spellLoc = null
        set unitLoc = null
        set caster = null
        
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger MassSleepTrg = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( MassSleepTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( MassSleepTrg, Condition( function Conditions ) )
        call TriggerAddAction( MassSleepTrg, function Actions )
        
        set victims = CreateGroup()
        set chooseVictims = Condition( function Pick )
        
        set bj_lastCreatedUnit = CreateUnit( Player( PLAYER_NEUTRAL_PASSIVE ), DUMMY_ID, 0, 0, 0 )
        call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID )
        call KillUnit( bj_lastCreatedUnit)
    endfunction
endscope

The wierd thing is that I get an error, and when I fix that error I get a new one, and when I fix it I get a new one and it goes on in an endless circle.
Now I'm just confused and don't know what to do with it :S
 
Ok, first things first:

JASS:
private function Range takes integer level returns real
        return I2R( 100 + level * 100 )
endfunction
This can be fixed by:
JASS:
private function Range takes integer level returns real
        return 100. + level * 100. //see the "." after the numbers; numbers with "." are always real numbers 
endfunction

And this:
JASS:
globals
        private group victims
        private boolexpr chooseVictims
        private unit target
endglobals
Should be:

JASS:
globals
        private group victims
        private boolexpr chooseVictims
        private unit tempCaster
    endglobals

Now function Pick:
JASS:
private function Pick takes nothing returns boolean
        return Targets( tempCaster, GetFilterUnits() )
    endfunction
Notice that I pass the caster of the spell, and the target as an argument.

JASS:
private function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and //all other stuff
endfunction

An the loop:
JASS:
loop
            set temp = FirstOfGroup( g )
            set unitLoc = GetUnitLoc( temp )
            exitwhen ( temp == null )
            call GroupRemoveUnit( group, temp)

            set dummy = CreateNUnitsAtLoc( 1, DUMMY_ID, GetOwningPlayer( caster ), unitLoc, 0.00 )
            call UnitAddAbility( dummy, DUMMY_SPELL_ID )
            call IssueTargetOrderBJ( dummy, "sleep", temp )
            call UnitApplyTimedLifeBJ( 1.50, 'BTLF', dummy )
        endloop

I think this is all you need to do. Your spell can be more improved, but fist I want you to understand what I did so far first.
 
Level 10
Joined
Aug 19, 2008
Messages
491
I changed everything as you said, except 1 code:
JASS:
private function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and //all other stuff
endfunction

I changed it to this:
JASS:
    private function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false )
    endfunction

And now I get 6 errors.
Jass is so case sensitive :xxd: I just gotta stick to it and hope I learn something.
 
Level 17
Joined
Jun 17, 2007
Messages
1,433
The tutorial is at least decent in some areas. Just a few points:
JASS:
if allies > enemies then
            loop
                set f = FirstOfGroup(all)
                exitwhen (f == null)
                call GroupRemoveUnit(all, f)
                //heal allies
            endloop
        elseif enemies > allies then
            loop
                set f = FirstOfGroup(all)
                exitwhen (f == null)
                call GroupRemoveUnit(all, f)
                //damage enemies
            endloop
        elseif allies == enemies then
            loop
                set f = FirstOfGroup(all)
                exitwhen (f == null)
                call GroupRemoveUnit(all, f)
                //heal allies and damage enemies
            endloop
        endif
Why wouldn't you just simplify that into:



JASS:
if allies > enemies then
            loop
                set f = FirstOfGroup(all)
                exitwhen (f == null)
                call GroupRemoveUnit(all, f)
                //heal allies
            endloop
        elseif enemies > allies then
            loop
                set f = FirstOfGroup(all)
                exitwhen (f == null)
                call GroupRemoveUnit(all, f)
                //damage enemies
            endloop
        else
            loop
                set f = FirstOfGroup(all)
                exitwhen (f == null)
                call GroupRemoveUnit(all, f)
                //heal allies and damage enemies
            endloop
        endif
Also, I'd cut out most of the explanation of vJASS features. Readers can find better tutorials elsewhere, and your tutorial is lengthy enough already.

Now, as good practice, we do NOT initialize our group in the block. Thing is some natives can and will crash the game if settled automatically. CreateGroup is one of those evil natives.
AFAIK, that is not true. When using a global group, I always have it initialized as CreateGroup() and have never had any problems associated with it. After taking a quick glance over at wc3c, I think it's worth mentioning GroupUtils initializes a group as CreateGroup().

Silence isn't a very ideal dummy spell. Although you removed most of the effects, it will still silence for 0.01 seconds, stopping any channeling spells. That is why it's ideal to use Channel instead.
 
Why wouldn't you just simplify that into:
That idea also occurred me, however I decided to use the other way because I think this way the code would be easier for the user to understand.
Also, I'd cut out most of the explanation of vJASS features. Readers can find better tutorials elsewhere, and your tutorial is lengthy enough already.
If all people think that way then why the hell are we making tutorials ?
As I said before, when I teach something I do it my way. If the user doesn't want to read no1 is pointing a gun and forcing him to do so. I give the basics and I also indicate more documentation for the user to read. So I am not changing it.

AFAIK, that is not true. When using a global group, I always have it initialized as CreateGroup() and have never had any problems associated with it. After taking a quick glance over at wc3c, I think it's worth mentioning GroupUtils initializes a group as CreateGroup().
This happens, if you believe me or not that is another question. I have a confident source over this matter, namely Griffen and Anitarf from wc3c, you can ask them.
Also, I won't tell people how to use systems. My intention is to teach them how to create codes, not how to use other people code. I intend to use TimerUtils for my next tutorial, but if I do so I also want to explain people how it works.
I am done improving this thing, I want someone to approve it. It has been almost an year.
 
Level 10
Joined
Aug 19, 2008
Messages
491
(Off-topic note: Sorry I didn't answer in a while, school is up again :bored:)


Here is the map it's in (so you can test it yourself withour recreating dummies): View attachment MassSleep vJass.w3x
And here is the current code I have:
JASS:
//***************************************************************************
//***** Mass Sleep
//***** Sleeps all units in an area.
//*****
//***** Area = 100 + 100 * level
//***** Duration = Change values in 'A001'
//***************************************************************************

scope MassSleep initializer Init
//***************************************************************************
//******************** SETUP ************************************************
//***************************************************************************
    
    globals 
        private constant integer MAIN_SPELL_ID = 'A000'
        private constant integer DUMMY_SPELL_ID = 'A001'
        private constant integer DUMMY_ID = 'h000'
    endglobals
    
    private function Range takes integer level returns real
        return 100. + level * 100. //see the "." after the numbers; numbers with "." are always real numbers
    endfunction
    
    private function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false )
      //return IsUnitEnemy( target, GetOwningPlayer( caster ) ) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false ) and IsUnitEnemy( target, GetOwningPlayer( GetTriggerUnit() ) )
    endfunction
    
//***************************************************************************
//****************** SETUP END **********************************************
//***************************************************************************

    globals
        private group victims
        private boolexpr chooseVictims
        private unit tempCaster
    endglobals

    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == MAIN_SPELL_ID
    endfunction
//===========================================================================
    private function Pick takes nothing returns boolean
        return Targets( tempCaster, GetFilterUnits() )
    endfunction
//===========================================================================
    private function Actions takes nothing returns nothing
        local location spellLoc = GetSpellTargetLoc()
        local real spellX = GetLocationX( spellLoc )
        local real spellY = GetLocationX( spellLoc )
        local integer level = GetUnitAbilityLevel( tempCaster, MAIN_SPELL_ID )
        local unit temp
        local unit dummy
        local location unitLoc
        set tempCaster = GetTriggerUnit()
        call GroupEnumUnitsInRange( victims, spellX, spellY, Range( level ), chooseVictims )
        
        loop
            set temp = FirstOfGroup( g )
            set unitLoc = GetUnitLoc( temp )
            exitwhen ( temp == null )
            call GroupRemoveUnit( group, temp)

            set dummy = CreateNUnitsAtLoc( 1, DUMMY_ID, GetOwningPlayer( caster ), unitLoc, 0.00 )
            call UnitAddAbility( dummy, DUMMY_SPELL_ID )
            call IssueTargetOrderBJ( dummy, "sleep", temp )
            call UnitApplyTimedLifeBJ( 1.50, 'BTLF', dummy )
        endloop
        
        call RemoveLocation( spellLoc )
        call RemoveLocation( unitLoc )
        set spellLoc = null
        set unitLoc = null
        set caster = null
        
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger MassSleepTrg = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( MassSleepTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( MassSleepTrg, Condition( function Conditions ) )
        call TriggerAddAction( MassSleepTrg, function Actions )
        
        set victims = CreateGroup()
        set chooseVictims = Condition( function Pick )
        
        set bj_lastCreatedUnit = CreateUnit( Player( PLAYER_NEUTRAL_PASSIVE ), DUMMY_ID, 0, 0, 0 )
        call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID )
        call KillUnit( bj_lastCreatedUnit)
    endfunction
endscope
 
Ok, code is fixed, please read ALL my comments =P

JASS:
//***************************************************************************
//***** Mass Sleep
//***** Sleeps all units in an area.
//*****
//***** Area = 100 + 100 * level
//***** Duration = Change values in 'A001'
//***************************************************************************

scope MassSleep initializer Init
//***************************************************************************
//******************** SETUP ************************************************
//***************************************************************************
    
    globals 
        private constant integer MAIN_SPELL_ID = 'A000'
        private constant integer DUMMY_SPELL_ID = 'A001'
        private constant integer DUMMY_ID = 'h000'
    endglobals
    
    private function Range takes integer level returns real
        return 100. + level * 100. //see the "." after the numbers; numbers with "." are always real numbers
    endfunction
    
    private function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false )
      //return IsUnitEnemy( target, GetOwningPlayer( caster ) ) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false ) and IsUnitEnemy( target, GetOwningPlayer( GetTriggerUnit() ) )
    endfunction
    
//***************************************************************************
//****************** SETUP END **********************************************
//***************************************************************************

    globals
        private group victims
        private boolexpr chooseVictims
        private unit tempCaster
    endglobals
//===========================================================================
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == MAIN_SPELL_ID
    endfunction
//===========================================================================
    private function Pick takes nothing returns boolean
    //replaced "GetFilterUnits()" with "GetFilterUnit()"
    //notice that this function only evaluates 1 unit at a time
        return Targets( tempCaster, GetFilterUnit() ) 
    endfunction
//===========================================================================
    private function Actions takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        //I added this new variable, to make code easier to read
        
        local location spellLoc = GetSpellTargetLoc()
        local real spellX = GetLocationX( spellLoc )
        local real spellY = GetLocationX( spellLoc )
        //You are using X and Y for SpellLoc, good job student!
        //Working with X and Y is a lot faster 
        //then working with location. Always use X and Y if you can. 
        
        local integer level = GetUnitAbilityLevel( tempCaster, MAIN_SPELL_ID )
        local unit temp
        local unit dummy
        
        //local location unitLoc -> We don't need this anymore! X and Y for the win!
        
        set tempCaster = caster
        call GroupEnumUnitsInRange( victims, spellX, spellY, Range( level ), chooseVictims )
        
        loop
        //set temp = FirstOfGroup( g ) -> group "g" does not exist !
            set temp = FirstOfGroup( victims )
            //set unitLoc = GetUnitLoc( temp ) -> We don't need this anymore! X and Y for the win!
            exitwhen ( temp == null )
            
            //call GroupRemoveUnit( group, temp) -> you are working with a group called "victims". Don't forget its name !
            call GroupRemoveUnit( victims, temp)
            
            //set dummy = CreateNUnitsAtLoc( 1, DUMMY_ID, GetOwningPlayer( caster ), unitLoc, 0.00 )
            //CreateNUnitsAtLoc -> returns a "group of several units" and not a single "unit"
            //If you only want to create 1 unit, use CreateUnit(), it is a lot faster and better!
            
            set dummy = CreateUnit(GetOwningPlayer(caster), DUMMY_ID, GetUnitX(temp), GetUnitY(temp), 0.00)
            
            call UnitAddAbility( dummy, DUMMY_SPELL_ID )
            
            //this stuff is Ok, but they are Bj's (evil in this case) 
            // I will optmize it
            //call IssueTargetOrderBJ( dummy, "sleep", temp )
            //call UnitApplyTimedLifeBJ( 1.50, 'BTLF', dummy )
            call IssueTargetOrder(dummy, "sleep", temp)
            call UnitApplyTimedLife(dummy, 'BTLF', 1.50)
            //there you go, see how easy it was? Remember to always use "Function List" menu so you can see if BJ functions are evil or not!
            
        endloop
        
        call RemoveLocation( spellLoc )
        //call RemoveLocation( unitLoc ) -> if we don't create it, we don't have to destroy it!
        set spellLoc = null
        //set unitLoc = null -> if we don't create it, we also don't beed to null it !
        set caster = null
        
        //added be Flame
        set dummy = null //don't forget to null the dummy unit!
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger MassSleepTrg = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( MassSleepTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( MassSleepTrg, Condition( function Conditions ) )
        call TriggerAddAction( MassSleepTrg, function Actions )
        
        //setting globals
        //good job!
        set victims = CreateGroup()
        set chooseVictims = Condition( function Pick )
        
        //Preloading ability
        //good job!
        set bj_lastCreatedUnit = CreateUnit( Player( PLAYER_NEUTRAL_PASSIVE ), DUMMY_ID, 0, 0, 0 )
        
        //call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID ) -> SPELL_ID does not exist! =P
        call UnitAddAbility( bj_lastCreatedUnit, DUMMY_SPELL_ID )
        
        call KillUnit( bj_lastCreatedUnit)
        
        //Always comment your codes, it makes them easier to read !
    endfunction
endscope
 
Errors:
1 - In Object Editor:
- Your hero has 2 spells based on silence. This will conflict. In order to avoid conflict, you have to give the hero a spell based on another ability (like Blizzard) or go to the field: "Text - Order String -Use/Turn On" and change it to something else but silence.

2 - In Code:
Changed
JASS:
local integer level = GetUnitAbilityLevel( tempCaster, MAIN_SPELL_ID )
to
JASS:
local integer level = GetUnitAbilityLevel( caster, MAIN_SPELL_ID )

(Can you find why line 1 was wrong?)

Changed:
JASS:
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX( spellLoc )
local real spellY = GetLocationX( spellLoc )

to

JASS:
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX( spellLoc )
local real spellY = GetLocationY( spellLoc )


(Can you find why code 1 was wrong? It is a very simple and dummy mistake xD)

Also, I decided to add this new line of code:
JASS:
call SetUnitAbilityLevel(dummy, DUMMY_SPELL_ID, level)
Can you tell me what it does ?

New code:
JASS:
//***************************************************************************
//***** Mass Sleep
//***** Sleeps all units in an area.
//*****
//***** Area = 100 + 100 * level
//***** Duration = Change values in 'A001'
//***************************************************************************

scope MassSleep initializer Init
//***************************************************************************
//******************** SETUP ************************************************
//***************************************************************************

    globals
        private constant integer MAIN_SPELL_ID = 'A000'
        private constant integer DUMMY_SPELL_ID = 'A001'
        private constant integer DUMMY_ID = 'h000'
    endglobals

    private function Range takes integer level returns real
        return 100. + level * 100. //see the "." after the numbers; numbers with "." are always real numbers
    endfunction

    private function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false )
      //return IsUnitEnemy( target, GetOwningPlayer( caster ) ) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false ) and ( IsUnitType( target, UNIT_TYPE_MAGIC_IMMUNE ) == false ) and IsUnitEnemy( target, GetOwningPlayer( GetTriggerUnit() ) )
    endfunction

//***************************************************************************
//****************** SETUP END **********************************************
//***************************************************************************

    globals
        private group victims
        private boolexpr chooseVictims
        private unit tempCaster
    endglobals
//===========================================================================
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == MAIN_SPELL_ID
    endfunction
//===========================================================================
    private function Pick takes nothing returns boolean
    //replaced "GetFilterUnits()" with "GetFilterUnit()"
    //notice that this function only evaluates 1 unit at a time
        return Targets( tempCaster, GetFilterUnit() )
    endfunction
//===========================================================================
    private function Actions takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        //I added this new variable, to make code easier to read

        local location spellLoc = GetSpellTargetLoc()
        local real spellX = GetLocationX( spellLoc )
        local real spellY = GetLocationY( spellLoc )
        //You are using X and Y for SpellLoc, good job student!
        //Working with X and Y is a lot faster
        //then working with location. Always use X and Y if you can.

        local integer level = GetUnitAbilityLevel( caster, MAIN_SPELL_ID )
        local unit temp
        local unit dummy
        
        //local location unitLoc -> We don't need this anymore! X and Y for the win!

        set tempCaster = caster
        call GroupEnumUnitsInRange( victims, spellX, spellY, Range( level ), chooseVictims )

        loop
        //set temp = FirstOfGroup( g ) -> group "g" does not exist !
            set temp = FirstOfGroup( victims )
            //set unitLoc = GetUnitLoc( temp ) -> We don't need this anymore! X and Y for the win!
            exitwhen ( temp == null )

            //call GroupRemoveUnit( group, temp) -> you are working with a group called "victims". Don't forget its name !
            call GroupRemoveUnit( victims, temp)

            //set dummy = CreateNUnitsAtLoc( 1, DUMMY_ID, GetOwningPlayer( caster ), unitLoc, 0.00 )
            //CreateNUnitsAtLoc -> returns a "group of several units" and not a single "unit"
            //If you only want to create 1 unit, use CreateUnit(), it is a lot faster and better!

            set dummy = CreateUnit(GetOwningPlayer(caster), DUMMY_ID, GetUnitX(temp), GetUnitY(temp), 0.00)

            call UnitAddAbility( dummy, DUMMY_SPELL_ID )
            
            //this line is new. Can you tell me what it does?
            call SetUnitAbilityLevel(dummy, DUMMY_SPELL_ID, level)
            
            //this stuff is Ok, but they are Bj's (evil in this case)
            // I will optmize it
            //call IssueTargetOrderBJ( dummy, "sleep", temp )
            //call UnitApplyTimedLifeBJ( 1.50, 'BTLF', dummy )
            
            call IssueTargetOrder(dummy, "sleep", temp)
            call UnitApplyTimedLife(dummy, 'BTLF', 5.00)
            //there you go, see how easy it was? Remember to always use "Function List" menu so you can see if BJ functions are evil or not!

        endloop

        call RemoveLocation( spellLoc )
        //call RemoveLocation( unitLoc ) -> if we don't create it, we don't have to destroy it!
        set spellLoc = null
        //set unitLoc = null -> if we don't create it, we also don't beed to null it !
        set caster = null

        //added be Flame
        set dummy = null //don't forget to null the dummy unit!
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger MassSleepTrg = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( MassSleepTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( MassSleepTrg, Condition( function Conditions ) )
        call TriggerAddAction( MassSleepTrg, function Actions )

        //setting globals
        //good job!
        set victims = CreateGroup()
        set chooseVictims = Condition( function Pick )

        //Preloading ability
        //good job!
        set bj_lastCreatedUnit = CreateUnit( Player( PLAYER_NEUTRAL_PASSIVE ), DUMMY_ID, 0, 0, 0 )

        //call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID ) -> SPELL_ID does not exist! =P
        call UnitAddAbility( bj_lastCreatedUnit, DUMMY_SPELL_ID )

        call KillUnit( bj_lastCreatedUnit)

        //Always comment your codes, it makes them easier to read !
    endfunction
endscope

I advise you to test my map, since I solved problem 1 by removing the first ability (Inner Justice) from the hero.
 

Attachments

  • vJass Spells.w3x
    30.9 KB · Views: 45
Level 10
Joined
Aug 19, 2008
Messages
491
(Off-topic note: I don't use Jass tags within quotes, since they're displayed wierd on my screen)

2 - In Code:
Changed

local integer level = GetUnitAbilityLevel( tempCaster, MAIN_SPELL_ID )


to

local integer level = GetUnitAbilityLevel( caster, MAIN_SPELL_ID )


(Can you find why line 1 was wrong?)

Yes.
We're are calling for tempCaster, which isn't declared in Locals.



Changed:

local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX( spellLoc )
local real spellY = GetLocationX( spellLoc )

to

local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX( spellLoc )
local real spellY = GetLocationY( spellLoc )

(Can you find why code 1 was wrong? It is a very simple and dummy mistake xD)
Yes! We're using 2 GetLocationX() in the first version!
Silly, silly me...




Also, I decided to add this new line of code:

call SetUnitAbilityLevel(dummy, DUMMY_SPELL_ID, level)

Can you tell me what it does ?
Well Yes! I can :thumbs_up:
If we examine the name I can clearly tell what it does. We could say
"Set Unit's Ability to Level".

call SetUnitAbilityLevel(Which Unit? (unit), What Ability? (string), What Level? (integer) )

All the used variables are declared above the function.
Now why do we want this function?
Simple; we want the level of the dummy's spell to be the same as the Hero's spell when cast, so we can change duration.



How many points do I get out of 10? :cute:
 
(Can you find why line 1 was wrong?)
Yes.
We're are calling for tempCaster, which isn't declared in Locals.
Kinda wrong. Notice that tempCaster is a global variable. You can call it at anyplace by any function.
However, try following the code execution. The problem of calling tempCaster in that line, is that tempCaster has no value there. We only make "set tempCaster = caster" after.

How many points do I get out of 10?
9 xD

I hope you understand the errors and mistakes you (and I) made here and I hope you learned this lesson well.

You should keep trying making more spells and don't stop making questions, they help you evolve.
 
Level 10
Joined
Aug 19, 2008
Messages
491
I hope you understand the errors and mistakes you (and I) made here and I hope you learned this lesson well.

Yeah I noticed that it's very easy to do mistakes like that "CallLocationX()" in Jass.

You should keep trying making more spells and don't stop making questions, they help you evolve.
As long as I have a reliable source that I can ask the most dumb question and still get a good answer with a nice attitude, I'll keep posting.
There are a few of those people now-a-days, including you Flame_Pheonix.

I'll work on my next spell today, though I'm not sure if I'll have time to post it :sad:
As I mentioned earlier, school is up again and I tend to have really busy periods.
When I can I think I'm gonna read through your tutorial again. The very best way to do this is if you could give me the PDF and I can read it on the train/bus when I'm bored.

EDIT: I took the time to start a thread in Admin Contact, requesting a review. I hope the right admin notices it.
Thumbs up for a great review :thumbs_up:

EDIT 2: Now I have questions for my next spell:
- I want to target a random point within a 900 circular area. How do I do this?
- When getting positions X and Y, do they refer to side/front or side/up?
- When mesuring Area of Effect, in this case 900, does the area recognize 900 as Diameter or Radius?

If you only answer the two lower ones I think I can manage the first one myself. In case I don't, I'll just come back here.
 
Last edited:
EDIT 2: Now I have questions for my next spell:
- I want to target a random point within a 900 circular area. How do I do this?
- When getting positions X and Y, do they refer to side/front or side/up?
- When mesuring Area of Effect, in this case 900, does the area recognize 900 as Diameter or Radius?
Mmm, I will try to answer your question "backwards".
3 - 900 is the diamter
2 - technically, in a 2D world, it is just like a Cartesian graph. However wc3c uses polar coordinates, so normal graphs have to be adapted. See this:
Polar coordinate system - Wikipedia, the free encyclopedia

1 - Well, if I am correct, you want to get a random point, in a circle. This function created by Vexorian does that for you:
JASS:
    //Function made by Vexorian, it selects a random region in a disk. 
    //All regions have the same chance of beeing choosen
    private function GetRandomPointInDisk takes real centerx, real centery, real minradius, real maxradius returns location
        local real d = SquareRoot(GetRandomReal(minradius * minradius, maxradius * maxradius))
        local real a = GetRandomReal(0, 2 * bj_PI)
        return Location(centerx + d * Cos(a), centery + d * Sin(a))
    endfunction
The code behind it is easy, however it uses very powerful maths behind it, that can be quite complex.

About the PDF; here is an old version of the tutorial. It may contain typos, but the main idea is there.
 

Attachments

  • vJASS Lesson 1x.pdf
    228.8 KB · Views: 109
Level 10
Joined
Aug 19, 2008
Messages
491
Oh and now I have a question for you:

In my next spell - which I don't wanna reveal too much of - I have this line:
JASS:
GetUnitCurrentOrder( caster ) != String2OrderIdBJ( MAIN_SPELL_ORDER )
As you can see they call for variables. Here they are:
JASS:
globals
    private constant string MAIN_SPELL_ORDER = "starfall"
endglobals

//In the same function as the action above I have this local:
    local unit caster = GetTriggerUnit()

Now, when I do this function
JASS:
 if GetUnitCurrentOrder( caster ) != String2OrderIdBJ( MAIN_SPELL_ORDER ) then
it returns False, which executes the actions (which I don't want cause it will end my loop).
Why is that?

I've tried to change the constant to a type like 'order' but nothing similar exists. I can't find what to change there :sad:
Can you fix it :cute:
 
Level 10
Joined
Aug 19, 2008
Messages
491
I don't understand your problem. If the unit is issued an order (starfall in this case) you want to execute the action right?

It's a "combo-loop" spell, where I wanna combine 2 loops. So if my caster's current order isn't "starfall" I wanna kill the dummy so his effect stops.

What happens is that the dummy's spell gets activated, but it instantly ends.
If I replace that function with "If 1 == 1 then" then it works fine (except that the dummy's spell will continue)

I will have some additional effects, but I don't wanna reveal too much. Would be awesome to surprise you with an epic spell, cause that would prove how much you've tought me the past week(s?)
 
Level 10
Joined
Aug 19, 2008
Messages
491
Wermm. I really don't get it, according to your description all seems fine =S

Haha I'll give it another shot :wink:

I want to combine 2 channeling spells, one is "target ground" and the other is "no target" ( = you click and is activated). If you give it a little brain crunching I think you can come up with 2 of those channeling spells.
What I wanna do is that when my Hero activates his channeling ability, I want to create a dummy unit, and when my Hero moves (or dies) I want the dummy to die.
For this to happen I created a function which activates itself every 0.2 seconds and checks if my Hero's current order active is "starfall" (now you have the no-target ability I talked about :grin:). This method worked in a spell I made before in GUI but then converted to Jass and asked for some help.

My "check if order" function looks something like this (I've removed a lot of it though):
JASS:
globals
    private constant string MAIN_SPELL_ORDER = "starfall"
endglobals

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

globals
    private unit dummy
endglobals

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

private function Effect1 takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit caster = GetTriggerUnit()
    
    if GetUnitCurrentOrder( caster ) != String2OrderIdBJ( MAIN_SPELL_ORDER ) then
        call KillUnit( dummy )
    else
        call TimerStart( t, 0.2, false, function Effect1 )
endfunction

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

private function Action takes nothing returns nothing
    set dummy = CreateUnit( p, DUMMY_ID, locX, locY, 0.00 )
    call TimerStart( t, 0.2, false, function Effect1 )
endfunction

Don't bother looking for mistakes in other lines, just something concerning the "GetUnitCurrentOrder" line.
Why?
Well, when I run the spell, the dummy is killed instantly, meaning that "if/then/else" function returns True.
If I replace "GetUnitCurrentOrder != (...)" with "1 != 1" (which is always false) the spell is activated fine, though the dummy never dies, which is what I want.

So, to summarize this, my question is:
Why does "GetUnitCurrentOrder( caster ) != String2OrderIdBJ( MAIN_SPELL_ORDER )" return true?
 
I want to combine 2 channeling spells, one is "target ground" and the other is "no target" ( = you click and is activated). If you give it a little brain crunching I think you can come up with 2 of those channeling spells.
I feared you wanted to do something like this ... There is a problem: your solution is highly ineffective and can not be explored. To make a decent spell out of this I would have to teach you how to use "Table" system, however I plan doing so for my tutorial number 3 (because using Table uses the computers gamecache. I see it as something experts do).

Your solution may work .... Let's give it a shot.

Seeing your code, I believe the problem is in the variable "caster".
Make a quick debug like this:

JASS:
private function Effect1 takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit caster = GetTriggerUnit()
    //checking the caster
    if caster == null then
        call BJDebugMsg("caster is null")
    else
        call BJDebugMsg("cast is Ok")
    endif

   //however the problem can also be the order
   call BJDebugMsg(OrderId2String(GetUnitCurrentOrder(caster)))

    if GetUnitCurrentOrder( caster ) != String2OrderIdBJ( MAIN_SPELL_ORDER ) then
        call KillUnit( dummy )
    else
        call TimerStart( t, 0.2, false, function Effect1 )
endfunction

There is little I can do to help you, your code seems fine.
Also, why is your dummy unit a global?
You know, that spell won't be MUI that way.
 
Level 10
Joined
Aug 19, 2008
Messages
491
Also, why is your dummy unit a global?
You know, that spell won't be MUI that way.

I wanted to make it global so I could transfer it between functions.
Perhaps there is a more efficient way of doing so?
I noticed that someone used a function called "call SetHandleHandle( t, "dummy", dummy)" but it didn't exist in vJass' library of functions.

And thank you! You made me see the problem. The caster is null.
Thanks :thumbs_up:

Now the problem is that I don't know how to detect him :/
Perhaps I can transfer GetTriggerUnit just like I transfer the dummy?
 
I wanted to make it global so I could transfer it between functions.
Perhaps there is a more efficient way of doing so?
I noticed that someone used a function called "call SetHandleHandle( t, "dummy", dummy)" but it didn't exist in vJass' library of functions.
Don't you EVER use that. That is mainly used by a system called Kattana's system which is not old, deprecated, and dangerously bugged.
It also uses gamecache.

Now the problem is that I don't know how to detect him :/
Perhaps I can transfer GetTriggerUnit just like I transfer the dummy?
You could make a globals variable for the caster indeed ... but it wouldn't be MUI.
There are many ways of doing what you want, you can even use an array system. However channel spells are a typical case of using systems like Table, PUI, DUI or UII.
I am going to be honest, you are trying to do something advanced for a beginner. My advice is: stay to instant spells for now.
 
Level 10
Joined
Aug 19, 2008
Messages
491
Don't you EVER use that. That is mainly used by a system called Kattana's system which is not old, deprecated, and dangerously bugged.
It also uses gamecache.


You could make a globals variable for the caster indeed ... but it wouldn't be MUI.
There are many ways of doing what you want, you can even use an array system. However channel spells are a typical case of using systems like Table, PUI, DUI or UII.
I am going to be honest, you are trying to do something advanced for a beginner. My advice is: stay to instant spells for now.

Ah well, I'll just disable it and do something else instead.
I'll edit or post when I have further questions.
 
Level 10
Joined
Aug 19, 2008
Messages
491
Not just a bit ... trust me xD

That was just a quote from a friend of mine :wink:
Personally I don't agree with him.

EDIT:
(Sorry for my lack of English, I'm exhausted from workout today :gg:)

I have yet another question for you, this time not about scripting but still about vJass:
Is there an easy way to convert vJass scripts into normal Jass?
For example, I have this friend who don't use vJass in his maps and never will - don't know why - so if he finds some of my spells nice and wants to copy them, he can't (I'm aware that I've made only 1 so far).
I came up with a solution myself - I thought - to add a syntax error in the end and copy the directly from the JASSHelper scripting check (or whatever its name is). Seemed fine, except for 1 thing:
Globals can't be declared in trigger (objects) in normal Jass, instead I found them on top of the Jass script (not vJass script).

So my real question is, how do I implent the globals of my spell into a map?
 
Last edited:
You can use the global editor ... Or you can follow the JESP standard for Jass.
Per exmaple, instead of having a global for a rawcode, just use a constant function in Jass.

There is however a problem. vJass allows the user to define new types of global variables such as attacktype and boolexpr (if I am not mistaken). Having in mind your sleep spell uses a boolexpr variable (per example), I think you simply can't convert it into Jass for a normal WE user, without doing something weird.

This matter is now out of my scope, I advise you to post at wc3campaigns. Having in mind the tool was created there,I am sure you will find professionals that can provide you with better assistance =D
 
Level 10
Joined
Aug 19, 2008
Messages
491
Hey, good news.
Remember that .pdf version you gave me?
Well I resized it, made the space between text and paper's border shorter and deleted all the [tag] marks (replacing them with proper text).

Here it is: View attachment vJass Session 1 (modifyed).doc

It's written in Word, so I'm not sure if you can open it.
I made this for myself cause your .pdf's
JASS:
 texts were confusing, but I thought 'Hey, what the hell. I'll release it on hive'
This document saves a lot of ink and paper (56.6% of original amount),
and all the [code=jass] tags are replaced with [FONT="Courier New"]Courier New[/FONT] fonts and made to fit the paper.
Hope you like it :thumbs_up:

: : EDIT : :

Hey whaddayaknow?
I started my spell about an hour ago and I finished it pretty early :grin:
Only 1 bug so far, everyting else seems fine :cool:
(The bug is that if you use it, and then another player uses it, it won't stack, and as you said I shouldn't begin to use timers just yet)
I'd just like you to take a look and give your professional opinion:
[code=jass]
//************************************************************************************
//**                : : Time Stop : :                                               **
//**                                                                                **
//**        Features:                                                               **
//**        - Pauses all non-hero and non-mechanical enemies in an area             **
//**          for a fixed amount of time.                                           **
//**                                                                                **
//**        Credits:                                                                **
//**        - Thanks to Flame_Pheonix @ hiveworkshop.com                            **
//**          for corrections                                                       **
//**                                                                                **
//**        THIS SPELL FOLLOWS THE JESP STANDARD                                    **
//**                                                                                **
//************************************************************************************

scope TimeStop initializer Init
//===========================================================================
//=============================== SETUP =====================================
//===========================================================================
    globals
        private constant integer SPELL_ID = 'A005'
        private constant integer DUMMY_ID = 'h000'
        private constant string PAUSE_EFFECT = "Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl"
    endglobals
    
    function Duration takes integer level returns real
        return 3. + ( 2. * level )
    endfunction
    
    function Range takes integer level returns real
        return 300. + ( 200 * level )
    endfunction
    
    function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy( target, GetOwningPlayer( caster ) ) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false) and ( IsUnitType( target, UNIT_TYPE_HERO ) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
    endfunction
//===========================================================================
//============================= END SETUP ===================================
//===========================================================================
    globals
        private boolexpr b
        private group all
        private group copy
        private unit tempCaster
    endglobals
//===========================================================================
    private function Pick takes nothing returns boolean
        return Targets( tempCaster, GetFilterUnit() )
    endfunction
//===========================================================================
    //This function was made by Blade_dk
    private function CopyGroup takes group g returns group
        set bj_groupAddGroupDest = CreateGroup()
        call ForGroup( g, function GroupAddGroupEnum )
        return bj_groupAddGroupDest
    endfunction
//===========================================================================
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ID
    endfunction
//===========================================================================
    private function Actions takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        local location spellLoc = GetUnitLoc( caster )
        local real spellX = GetLocationX( spellLoc )
        local real spellY = GetLocationY( spellLoc )
        local unit f
        local real unitX
        local real unitY
        local location unitLoc
        local integer level = GetUnitAbilityLevel( caster, SPELL_ID )
        set tempCaster = caster
        
        call GroupEnumUnitsInRange( all, spellX, spellY, Range( level ), b )
        set copy = CopyGroup( all )
        
        //Pausing the units
        loop
            set f = FirstOfGroup( copy )
            set unitLoc = GetUnitLoc( f )
            set unitX = GetLocationX( unitLoc )
            set unitY = GetLocationY( unitLoc )
            exitwhen f == null
            call PauseUnitBJ( true, f )
            call DestroyEffect( AddSpecialEffect( PAUSE_EFFECT, unitX, unitY) )
            call GroupRemoveUnit( copy, f )
        endloop
        
        //Duration
        call TriggerSleepAction( Duration( level ) )

        //Unpausing the units
        loop
            set f = FirstOfGroup( all )
            exitwhen f == null
            call PauseUnitBJ( false, f )
            call GroupRemoveUnit( all, f )
        endloop

        //Cleaning Leaks
        call RemoveLocation( spellLoc )
        set spellX = 0.
        set spellY = 0.
        set f = null
        set caster = null
        set level = 0
        
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger trg = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( trg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( trg, Condition( function Conditions ) )
        call TriggerAddAction( trg, function Actions )
        set trg = null
        
        //globals
        set b = Condition( function Pick )
        set all = CreateGroup()
        
        //preload effects
        call Preload( PAUSE_EFFECT )

        //preload ability
        set bj_lastCreatedUnit = CreateUnit(Player( PLAYER_NEUTRAL_PASSIVE ), DUMMY_ID, 0, 0, 0)
        call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID )
        call KillUnit( bj_lastCreatedUnit )

    endfunction
endscope
 
Last edited:
Good spell, I am impressed, it almost looks a professional spell except for a few things =P
To make your spell MUI, the group "copy" must be a local group (inside function Actions) and not a global variable. You should only use global variables for instant things (picking all units near a hero is an instant action, however saving all units, wait the duration of the spell, and unpause all units, is NOT instant).

Also, don't use GetUnitLoc(f), use instead GetUnitX(f) and GetUnittY(f)

Also, why do you get the location of the spell if you can get it via the caster?
Try using "spellX = GetUnitX(caster)" (same for spellY).
Never
use locations unless you have a Z offset (which is not the case, you only need the X and Y).

Also, try commenting your SETUP function better, helps newbies import the spell =D

I will give the code a better look later, and Thx for the word document =D
 
Level 10
Joined
Aug 19, 2008
Messages
491
Good spell, I am impressed, it almost looks a professional spell except for a few things =P
To make your spell MUI, the group "copy" must be a local group (inside function Actions) and not a global variable. You should only use global variables for instant things (picking all units near a hero is an instant action, however saving all units, wait the duration of the spell, and unpause all units, is NOT instant).

Also, don't use GetUnitLoc(f), use instead GetUnitX(f) and GetUnittY(f)

Also, why do you get the location of the spell if you can get it via the caster?
Try using "spellX = GetUnitX(caster)" (same for spellY).
Never
use locations unless you have a Z offset (which is not the case, you only need the X and Y).

Also, try commenting your SETUP function better, helps newbies import the spell =D

I will give the code a better look later, and Thx for the word document =D

I didn't know there was a function called "GetUnitX( f )" :sad: If would've helped :xxd:
Okay, I'll make copy into a local, will update this post later with the new code (or a new post depending on if you've answered or not).
Thank you for your professional opinion, this is precisly what I wanted :thumbs_up:
 
Level 10
Joined
Aug 19, 2008
Messages
491
Here's the new code:
It's got a better header and the GetUnitXY function (hope I didn't miss anything I did this sort of hasty :xxd:

JASS:
//************************************************************************
//**                                                                    **
//**                        ~Time Stop~                                 **
//**        Created by Cheezeman @ [url]www.hiveworkshop.com[/url]      **
//**          Made by Cheezeman @ [url]www.hiveworkshop.com[/url]       **
//**                                                                    **
//**                                                                    **
//**        Features:                                                   **
//**        - Pauses the time for all enemies within a certain area     **
//**          Everything is modifyable in the Setup section, all you    **
//**          need is a blank AHtc for your Hero.                       **
//**                                                                    **
//**        Known bugs:                                                 **
//**        - This spell won't stack. If used twice on the same         **
//**          group the group will still be unpaused after the          **
//**          original time.                                            **
//**                                                                    **
//**        Version 1.1                                                 **
//**                                                                    **
//************************************************************************

scope TimeStop initializer Init
//===========================================================================
//=============================== SETUP =====================================
//===========================================================================
    globals
        private constant integer SPELL_ID = 'A005'
        private constant integer DUMMY_ID = 'h000'
        private constant string PAUSE_EFFECT = "Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl"
    endglobals
    
    function Duration takes integer level returns real
        return 3. + ( 2. * level )
    endfunction
    
    function Range takes integer level returns real
        return 150. + ( 100 * level )
    endfunction
    
    function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy( target, GetOwningPlayer( caster ) ) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false) and ( IsUnitType( target, UNIT_TYPE_HERO ) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
    endfunction
//===========================================================================
//============================= END SETUP ===================================
//===========================================================================
    globals
        private boolexpr b
        private group all
        private unit tempCaster
    endglobals
//===========================================================================
    private function Pick takes nothing returns boolean
        return Targets( tempCaster, GetFilterUnit() )
    endfunction
//===========================================================================
    //This function was made by Blade_dk
    private function CopyGroup takes group g returns group
        set bj_groupAddGroupDest = CreateGroup()
        call ForGroup( g, function GroupAddGroupEnum )
        return bj_groupAddGroupDest
    endfunction
//===========================================================================
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ID
    endfunction
//===========================================================================
    private function Actions takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        local real spellX = GetUnitX( caster )
        local real spellY = GetUnitY( caster )
        local unit f
        local real unitX
        local real unitY
        local location unitLoc
        local integer level = GetUnitAbilityLevel( caster, SPELL_ID )
        local group copy
        set tempCaster = caster
        
        call GroupEnumUnitsInRange( all, spellX, spellY, Range( level ), b )
        set copy = CopyGroup( all )
        
        //Pausing the units
        loop
            set f = FirstOfGroup( copy )
            set unitX = GetUnitX( f )
            set unitY = GetUnitY( f )
            exitwhen f == null
            call PauseUnitBJ( true, f )
            call DestroyEffect( AddSpecialEffect( PAUSE_EFFECT, unitX, unitY) )
            call GroupRemoveUnit( copy, f )
        endloop
        
        //Duration
        call TriggerSleepAction( Duration( level ) )

        //Unpausing the units
        loop
            set f = FirstOfGroup( all )
            exitwhen f == null
            call PauseUnitBJ( false, f )
            call GroupRemoveUnit( all, f )
        endloop

        //Cleaning Leaks
        set spellX = 0.
        set spellY = 0.
        set unitX = 0.
        set unitY = 0.
        set f = null
        set caster = null
        set level = 0
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger trg = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( trg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( trg, Condition( function Conditions ) )
        call TriggerAddAction( trg, function Actions )
        set trg = null
        
        //globals
        set b = Condition( function Pick )
        set all = CreateGroup()
        
        //preload effects
        call Preload( PAUSE_EFFECT )

        //preload ability
        set bj_lastCreatedUnit = CreateUnit(Player( PLAYER_NEUTRAL_PASSIVE ), DUMMY_ID, 0, 0, 0)
        call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID )
        call KillUnit( bj_lastCreatedUnit )

    endfunction
endscope
 
Last edited:
Ok about your spell:
JASS:
local location unitLoc
You don't need this, you are not using it.

You have to initialize your group variable called "copy" by making "set copy = CreateGroup()" or "local group copy = CreateGroup()".
Also, you are pausing the unit on group copy, that is missplaced, you should be pausing the units from group "all" and then unpause units from group "copy". Why? Because group "all" is a global group with ALL units from all instances of your spell. A global group like group "all" must be only used in an instant situation, however you are making a long wait, and then modifying the group, which is wrong. If you use group "all" to pause units, this will happen before the wait and so it will be instant.

When you create a group, you also have to destroy it. For such an effect do "call DestroyGroup(copy)" and after that "set copy = null".

About nulling variables, you don't need to null reals nor integers not triggers.
So, this means:
JASS:
set spellX = 0.
set spellY = 0.
set unitX = 0.
set unitY = 0.
set trg = null
Are not necessary. They don't leak bad.

You also have another unnecessary nullification:
JASS:
set f = null

Why you don't need to null "f" ??
I give you a hint, look at your last "loop" statement. What is the condition you need to exit the loop?

I am going to teach you soon how to use a few small simple systems, but first the code must be cleaned =D
 
Level 10
Joined
Aug 19, 2008
Messages
491
JASS:
local location unitLoc
You don't need this, you are not using it.
Nope, I don't. Fixed.

You have to initialize your group variable called "copy" by making "set copy = CreateGroup()" or "local group copy = CreateGroup()".
Why?
Ok I will do it, but why? It worked fine without it.

Important note about me
I'm very curious about reasons. If you tell me something like this you don't have to tell me why, I will still do it since I belive you.

Also, you are pausing the unit on group copy, that is missplaced, you should be pausing the units from group "all" and then unpause units from group "copy".
Alright, fixed. I would've figured that myself from your statment before but I didn't notice it.

When you create a group, you also have to destroy it. For such an effect do "call DestroyGroup(copy)" and after that "set copy = null".
What, I didn't destroy it?
Aw geez...
Anyway fixed, I miss a lot of things by mistake.

About nulling variables, you don't need to null reals nor integers not triggers.
So, this means:
JASS:
set spellX = 0.
set spellY = 0.
set unitX = 0.
set unitY = 0.
set trg = null
Are not necessary. They don't leak bad.
Well, in my own opinion I think all variables (except for globals and similar of course) should be nullified.
I know that reals, integers and trigger variables almost don't leak, but in the very very very long run it's a good thing to have them null too.
Not that it matters at all in such a small map as mine, but if someone wants to put them in their great great great RPG, I think that dude wants it to be 100% leak free.

You also have another unnecessary nullification:
JASS:
set f = null

Why you don't need to null "f" ??
Well, after the loop, f is always null (due to the "exitwhen f == null")
It was just a reflex by me to nullify it.



I am going to teach you soon how to use a few small simple systems, but first the code must be cleaned =D
I've got one word for you:
Yay! :grin:
I guess it's got something to do with libraries, doesn't it?
I'll save my other questions until you have it ready, I don't wanna put any pressure on you...

Anyway here's the new code:
JASS:
//************************************************************************
//**                                                                    **
//**                        ~Time Stop~                                 **
//**        Created by Cheezeman @ [url]www.hiveworkshop.com[/url]      **
//**          Made by Cheezeman @ [url]www.hiveworkshop.com[/url]       **
//**                                                                    **
//**                                                                    **
//**        Features:                                                   **
//**        - Pauses the time for all enemies within a certain area     **
//**          Everything is modifyable in the Setup section, all you    **
//**          need is a blank AHtc for your Hero.                       **
//**                                                                    **
//**        Known bugs:                                                 **
//**        - This spell won't stack. If used twice on the same         **
//**          group the group will still be unpaused after the          **
//**          original time.                                            **
//**                                                                    **
//**        Version 1.1                                                 **
//**                                                                    **
//************************************************************************

scope TimeStop initializer Init
//===========================================================================
//=============================== SETUP =====================================
//===========================================================================
    globals
        private constant integer SPELL_ID = 'A003'
        private constant integer DUMMY_ID = 'h000'
        private constant string PAUSE_EFFECT = "Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl"
    endglobals
    
    function Duration takes integer level returns real
        return 3. + ( 2. * level )
    endfunction
    
    function Range takes integer level returns real
        return 150. + ( 100 * level )
    endfunction
    
    function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy( target, GetOwningPlayer( caster ) ) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false) and ( IsUnitType( target, UNIT_TYPE_HERO ) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
    endfunction
//===========================================================================
//============================= END SETUP ===================================
//===========================================================================
    globals
        private boolexpr b
        private group all
        private unit tempCaster
    endglobals
//===========================================================================
    private function Pick takes nothing returns boolean
        return Targets( tempCaster, GetFilterUnit() )
    endfunction
//===========================================================================
    //This function was made by Blade_dk. Visit [url]www.wc3c.com[/url] for more info..
    private function CopyGroup takes group g returns group
        set bj_groupAddGroupDest = CreateGroup()
        call ForGroup( g, function GroupAddGroupEnum )
        return bj_groupAddGroupDest
    endfunction
//===========================================================================
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ID
    endfunction
//===========================================================================
    private function Actions takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        local real spellX = GetUnitX( caster )
        local real spellY = GetUnitY( caster )
        local unit f
        local real unitX
        local real unitY
        local integer level = GetUnitAbilityLevel( caster, SPELL_ID )
        local group copy = CreateGroup()
        set tempCaster = caster
        
        call GroupEnumUnitsInRange( all, spellX, spellY, Range( level ), b )
        set copy = CopyGroup( all )
        
        //Pausing the units
        loop
            set f = FirstOfGroup( all )
            set unitX = GetUnitX( f )
            set unitY = GetUnitY( f )
            exitwhen f == null
            call PauseUnitBJ( true, f )
            call DestroyEffect( AddSpecialEffect( PAUSE_EFFECT, unitX, unitY) )
            call GroupRemoveUnit( all, f )
        endloop
        
        //Duration
        call TriggerSleepAction( Duration( level ) )

        //Unpausing the units
        loop
            set f = FirstOfGroup( copy )
            exitwhen f == null
            call PauseUnitBJ( false, f )
            call GroupRemoveUnit( copy, f )
        endloop

        //Cleaning Leaks
        call DestroyGroup( copy )
        set copy = null
        set spellX = 0.
        set spellY = 0.
        set unitX = 0.
        set unitY = 0.
        set caster = null
        set level = 0
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger trg = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( trg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( trg, Condition( function Conditions ) )
        call TriggerAddAction( trg, function Actions )
        set trg = null
        
        //globals
        set b = Condition( function Pick )
        set all = CreateGroup()
        
        //preload effects
        call Preload( PAUSE_EFFECT )

        //preload ability
        set bj_lastCreatedUnit = CreateUnit(Player( PLAYER_NEUTRAL_PASSIVE ), DUMMY_ID, 0, 0, 0)
        call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID )
        call KillUnit( bj_lastCreatedUnit )

    endfunction
endscope
 
Why?
Ok I will do it, but why? It worked fine without it.
If you don't initialize groups with CreateGroup() you will have a code that will be buggy. Good coding tries to avoid buggy situations from the start. Just because it works (sometimes) it doesn't mean it is a good idea. It is like Effects, or Timers, you just have to create them first.

Well, in my own opinion I think all variables (except for globals and similar of course) should be nullified.
I know that reals, integers and trigger variables almost don't leak, but in the very very very long run it's a good thing to have them null too.
Not that it matters at all in such a small map as mine, but if someone wants to put them in their great great great RPG, I think that dude wants it to be 100% leak free.
Do you know what is the difference between leaking an integer with value "0" and with value "16" ? I tell you: none. And that is what you are doing by "fixing" the leaks, tou are leaking integers and reals with value 0 lol...
Which is why coders do not clean such things, it is useless, and you are not gaining any real advantage, in fact, you are making you code slower because you are making unnecessary steps of cleaning.
Efficiency in RPG's (or specially ORPG's) is crucial, fast codes are important. Don't worry, after deleting those lines, you won't have any real leak problems (since you can't do anything about it, why sacrifice efficiency for no reason?).
I guess it's got something to do with libraries, doesn't it?
Yes it is. It is a very simple library, but with an important concept.
Soon my friend, give time to time.

About your code:
You have a SETUP section. This section is the most important (and only important) section of your spell for any user. Why? Because it is the only thing they will care about your code. The user trusts the coder did a good job with the spell core, and the user only changes what he needs (which is why headers with information are very important, the user may want to search more information about your spell, or about YOU ). Please add comments to the functions and global variables of the SETUP section.

Other than that, please delete the unnecessary clean steps.

Ohh and btw:
Important note about me
I'm very curious about reasons. If you tell me something like this you don't have to tell me why, I will still do it since I belive you.
Don't. Don't believe me, your curiosity is a crucial gift, it compels you to evolve and learn and that is a very good thing. However expect people to get frustrated if you refuse to obey them or if you refuse to listen to them, when people can't justify themselves with valid reasons, that usually happens.
I believe I justified myself with valid reasons, so I hope you evolve further and faster.
However, you must know (and probably will) that I am not God nor Chuck Norris, I will fail a justification sooner or later and when that time comes, you must search on yourself in THW and wc3c or in google for an answer.
 
Level 4
Joined
Mar 14, 2009
Messages
98
Actually...by adding that CreateGroup() at the start, you actually made it leak. It worked without CreateGroup because the function CopyGroup already creates a group and returns that group it created. Now what happens to the group you created at the very start? Let's start off by defining something important: handle variables.

Well, handle variables are things that point to places where things are stored, they don't really store things. If you know C, they're what you would call pointers, but whatever. What are handle variables you ask? Any variable that is not an integer, real, string, or boolean is a handle variable. Anyway, as I was saying, they point to where things are located. Now they can only point to one thing of course. So once you make it point to something else, how can you access what it once pointed at? You can't, unless you have another handle pointing to that once-pointed-at-thing. And that's what leaking is. No handles pointing to something, therefore you have an inaccessible memory waster that you can never get rid of.

You also have to null handle variables because they suck. Yes, they suck. Warcraft somehow doesn't know when a handle variable points to nothing anymore(e.g. a group was destroyed) unless it's nulled, so if it's not nulled the handle variable remains and it eats up memory. Anyway, back to the original problem.

The group you created at the start of the function was being pointed to by the handle variable "copy". And then, you set "copy" to the group created by the function CopyGroup. So the original group you made? Nothing pointing to it, so we have a leak. Quite accidental of course, and only in this specific case because of CopyGroup, it's not a fault with Flame_Phoenix. I was only browsing and I actually looked at the code when you said it worked without initializing the group, so I noticed it. Normally, you'd get a bunch of error messages if the group wasn't initialized.

Oh, and Warcraft is smart enough to know when integers, reals, strings, and booleans aren't used anymore, so they can't leak.
 
Level 10
Joined
Aug 19, 2008
Messages
491
@Peetoon
Thanks! :thumbs_up:
Though I'm not really sure what to do with the information :hohum:
Do I add or remove "= CreateGroup()"?

I mean, if I do it, it wont bug.
If I don't, it won't leak.

:confused:

- - - - -

Anyway this is my new code.
Changes:
- Added lots of info in Setup
- Removed useless nullifications
- Made all the functions inside the scope private (huge mistake by me :xxd: didn't notice it until I copied the trigger and saved)
- Added a new feature; to be able to say if it's a Target Ground or No Target spell. (It actualy works fine, you can have the map if you wanna test).



JASS:
//************************************************************************
//**                                                                    **
//**                        ~Time Stop~                                 **
//**        Created by Cheezeman @ [url]www.hiveworkshop.com[/url]                 **
//**          Made by Cheezeman @ [url]www.hiveworkshop.com[/url]                  **
//**                                                                    **
//**                                                                    **
//**        Features:                                                   **
//**        - Pauses the time for all enemies within a certain area     **
//**          Everything is modifyable in the Setup section, all you    **
//**          need is a blank AHtc for your Hero.                       **
//**                                                                    **
//**        Known bugs:                                                 **
//**        - This spell won't stack. If used twice on the same         **
//**          group the group will still be unpaused after the          **
//**          original time.                                            **
//**                                                                    **
//**        Note:                                                       **
//**        - You HAVE to change the scope's name to the trigger's      **
//**          name, else this won't compile.                            **
//**                                                                    **
//**        Version 1.1                                                 **
//**                                                                    **
//************************************************************************

scope TimeStopTarget initializer Init
//===========================================================================
//=============================== SETUP =====================================
//===========================================================================
    globals
        private constant integer SPELL_ID = 'A006'
        //The rawcode of the spell. Rawcodes can be found in Object Editor by marking the window and pressing Ctrl + D,
        //and then looking on the object.
        
        private constant integer DUMMY_ID = 'h000'
        //The rawcode of the dummy unit. Detected same way as SPELL_ID.
        //If you don't have one in your map, just copy this one's, or visit [url]http://www.hiveworkshop.com/forums/f279/basics-trigger-enhancing-spells-7334/[/url] to understand their use.
        
        private constant string PAUSE_EFFECT = "Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl"
        //This is the effect you see on the paused units when you use the spell.
        //You can find the location by changing [Thunder Clap => Art - Caster] and then copying the "Custom Value" which appears
        //when you re-enter the data field.
        //DON'T FORGET THE DOUBLE "\\"
    endglobals
    
    
    
    private function NoTarget takes nothing returns boolean
        return true
        //If you check this as 'false', it means that your spell targets an area on the ground,
        //and not around the caster as it was originally designed.
        //Remember to change Range so it coresponds to the spell's!
    endfunction
    
    
    
    //The duration of time stop on the targeted units
    private function Duration takes integer level returns real
        return 3. + ( 2. * level )
    endfunction
    
    
    
    //The range of the spell (in diameter I belive)
    private function Range takes integer level returns real
        return 200. + ( 50. * level )
    endfunction
    
    
    
    //These are the allowed targets. Currently, the target have to be an enemy and it can't be either dead, a building, a Hero, magic immune or mechanical
    private function Targets takes unit caster, unit target returns boolean
        return IsUnitEnemy( target, GetOwningPlayer( caster ) ) and ( GetWidgetLife( target ) > 0.405 ) and ( IsUnitType( target, UNIT_TYPE_STRUCTURE ) == false) and ( IsUnitType( target, UNIT_TYPE_HERO ) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
    endfunction
//===========================================================================
//============================= END SETUP ===================================
//===========================================================================
    globals
        private boolexpr b
        private group all
        private unit tempCaster
    endglobals
//===========================================================================
    private function Pick takes nothing returns boolean
        return Targets( tempCaster, GetFilterUnit() )
    endfunction
//===========================================================================
    //This function was made by Blade_dk. Visit [url]www.wc3c.com[/url] for more info..
    private function CopyGroup takes group g returns group
        set bj_groupAddGroupDest = CreateGroup()
        call ForGroup( g, function GroupAddGroupEnum )
        return bj_groupAddGroupDest
    endfunction
//===========================================================================
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ID
    endfunction
//===========================================================================
    private function Actions takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        //Caster's location
        local real casterX = GetUnitX( caster )
        local real casterY = GetUnitY( caster )
        //Spell's location
        local location spellLoc = GetSpellTargetLoc()
        local real spellX = GetLocationX( spellLoc )
        local real spellY = GetLocationY( spellLoc )
        //The loop target
        local unit f
        local real unitX
        local real unitY
        //Misc
        local integer level = GetUnitAbilityLevel( caster, SPELL_ID )
        local group copy = CreateGroup()
        set tempCaster = caster

        //Checking if the spell is target or no-target
        if ( NoTarget() == true ) then
            call GroupEnumUnitsInRange( all, casterX, casterY, Range( level ), b )
        else
            call GroupEnumUnitsInRange( all, spellX, spellY, Range( level ), b )
        endif
        set copy = CopyGroup( all )
        
        //Pausing the units
        loop
            set f = FirstOfGroup( all )
            set unitX = GetUnitX( f )
            set unitY = GetUnitY( f )
            exitwhen f == null
            call PauseUnitBJ( true, f )
            call DestroyEffect( AddSpecialEffect( PAUSE_EFFECT, unitX, unitY) )
            call GroupRemoveUnit( all, f )
        endloop
        
        //Duration
        call TriggerSleepAction( Duration( level ) )

        //Unpausing the units
        loop
            set f = FirstOfGroup( copy )
            exitwhen f == null
            call PauseUnitBJ( false, f )
            call GroupRemoveUnit( copy, f )
        endloop

        //Cleaning Leaks
        call DestroyGroup( copy )
        call RemoveLocation( spellLoc )
        set copy = null
        set spellLoc = null
        set caster = null
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        local trigger trg = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( trg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( trg, Condition( function Conditions ) )
        call TriggerAddAction( trg, function Actions )
        set trg = null
        
        //globals
        set b = Condition( function Pick )
        set all = CreateGroup()
        
        //preload effects
        call Preload( PAUSE_EFFECT )

        //preload ability
        set bj_lastCreatedUnit = CreateUnit(Player( PLAYER_NEUTRAL_PASSIVE ), DUMMY_ID, 0, 0, 0)
        call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID )
        call KillUnit( bj_lastCreatedUnit )

    endfunction
endscope
 
Oh, and Warcraft is smart enough to know when integers, reals, strings, and booleans aren't used anymore, so they can't leak.
It looks I am not actualized ... where did you get this information from?

Though I'm not really sure what to do with the information
Do I add or remove "= CreateGroup()"?

I mean, if I do it, it wont bug.
If I don't, it won't leak.
Actually from hat I understood: if you don't use "CreateGroup" you are leaking -> which is bad. I may have understood wrong ... but I doubt it.

As for the code, I will give it another look tomorrow, I can't do it right now. In the SETUP section there is also an extra thing you should do: Make all functions you can constant. Why? Well, according to Daelin, it is faster. However be careful, functions that make non-constant operations can not be used. So, this basically means that only functions Duration and Range should be constant. It is also a nice standard some people (like me) use xD.
 
Level 4
Joined
Mar 14, 2009
Messages
98
Normally, when a function ends in other languages, all the locals are thrown away and the space they took up in the memory would be released. In warcraft, that only works for booleans, integers, reals, and strings (anything not extended by variable type handle). At least, that's what I gather from what I've read.

As to your problem, don't worry, it won't bug if you don't put the "= CreateGroup()" at the start. Let's look at the code for proof:

JASS:
    set copy = CopyGroup( all )//By doing this, you're calling a function, 
    //then saving whatever it returns to the variable. Let's look at the function...

    private function CopyGroup takes group g returns group
        set bj_groupAddGroupDest = CreateGroup() //This creates a group.
        call ForGroup( g, function GroupAddGroupEnum )
        //This adds all the units in the first group to the newly created group.
        //Just to be clear, ForGroup is like a loop that goes through all the
        //units in a group and calls a function for each of them.
        return bj_groupAddGroupDest //This returns the newly created group.
    endfunction

    function GroupAddGroupEnum takes nothing returns nothing
        call GroupAddUnit(bj_groupAddGroupDest, GetEnumUnit())
    endfunction
    //In this situation, this is the function that's called by the ForGroup "loop".
   //It adds a unit to the global group bj_groupAddGroupDest. 
   //GetEnumUnit() is like the GetFilterUnit() for ForGroup "loops".
   //bj_groupAddGroupDest is a blizzard defined global by the way.
   //You can tell from the bj_ at the start.
   //I think I added more comments than code I copied.

Notice how the function you called creates a new group, adds each unit from the source group to it, and then returns it. As explained earlier, by setting a handle, you're making it point to something. Group is a handle variable, hence by not setting it, it's not pointing to anything. Adding a unit to it would be like telling someone to "put this in that box" in a room full of boxes without pointing at any specific box, but in this case you're setting the group variable Copy to point to the group created by the function CopyGroup. Hence, it won't bug because it will always point to the group created by CopyGroup. (Unless, of course, you prematurely destroy that group.)

Edit: On a side note, I believe that range is radius, not diameter. I could be wrong though, but I'm pretty sure it's right.

What's with the PauseUnitBJ? PauseUnit does the exact same thing, with the parameter order reversed. It takes a unit first, then a boolean. If there's a native version of a BJ function, call the native version, especially if the BJ function is just a swapped copy of the native version. It's faster that way.
 
Status
Not open for further replies.
Top