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

[Lua] Need help with boolexpr

Status
Not open for further replies.
Level 3
Joined
Sep 1, 2009
Messages
37
15 year old me used to make simple spells in gui 10 years ago and jass was too daunting to me. With the release of reforged beta and its support of lua it seems like the perfect time to get back in. I have no experience with lua either but I wanna learn it.

Now to the question, Im not sure about how boolexpr is different from boolean? I was trying out this:
Lua:
function blizzardEffect()
    local trigger = CreateTrigger()
    TriggerRegisterAnyUnitEvent(trigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    TriggerAddAction(trigger, function()
        local caster = GetTriggerUnit()
        if GetSpellAbilityId() == FourCC('AHbz') then
            local unitGroup = GetUnitsInRangeOfLocMatching(500, GetSpellTargetLoc(), Condition(function()
                return GetFilterUnit() ~= caster
            end))
            ForGroup(unitGroup, function()
                local u = GetEnumUnit()
                IssueTargetOrder(u, 'attack', GroupPickRandomUnit(unitGroup)) 
            end)
        end
    end)
end

It works with GetUnitsInRangeOfLoc() but not with the condition. So while trying to figure out why I tried this:
Lua:
function doSth(a,b)
    print(a,b)
    if b == true then
        return a + 1
    else
        return a
    end
end

function sth()
   local unit = 1
   return doSth(unit, function()
       return true
       end)
end

print(sth())

where print(a,b) prints the address of function b instead of return value true, why is this happening and how do I fix it?
 

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,888
Lua uses dynamic types, if you tried to print a unit
Lua:
print(GetTriggerUnit())
it will display the wc3 type, its handle id following with some other weird numbers. Something similar hapepns to the boolexpr.
I'm not exactly sure what you're trying to do with print though.
These links might help you understand it better :)
Lua 5.3 Reference Manual
Lua Tutorial - Tutorialspoint
 
Level 3
Joined
Sep 1, 2009
Messages
37
Lua uses dynamic types, if you tried to print a unit
Lua:
print(GetTriggerUnit())
it will display the wc3 type, its handle id following with some other weird numbers. Something similar hapepns to the boolexpr.
I'm not exactly sure what you're trying to do with print though.
These links might help you understand it better :)
Lua 5.3 Reference Manual
Lua Tutorial - Tutorialspoint

I didnt run the second part through warcraft 3 it was only for testing purposes, Im not trying to print stuff I just wanna see its return value. See I dont know how this function is supposed to work
Lua:
GetUnitsInRangeOfLocMatching(range, target, condition)

Does it have some for loop in it that adds units one by one checking if the condition is true or false? In my second example local unit is set to 1 then being passed into doSth(a,b) as 'a' while 'b' is a function that should return true always and if b is true then sth() should return 2 no? But it returns 1 and the value of the anonymous function being passed as a parameter 'b' isnt true but instead an address.

But as for the warcraft 3 spell the only thing I was trying to acomplish with the condition is excluding the caster from the unit group.
 
Level 3
Joined
Sep 1, 2009
Messages
37
Yes. A boolexpr is a boolean expression: it evaluates some expression/performs some operations and then returns a boolean.

So can you tell me why this doesnt work?
Lua:
            local unitGroup = GetUnitsInRangeOfLocMatching(500, GetSpellTargetLoc(), Condition(function()
                return GetFilterUnit() ~= caster
            end))

This works: it creates the unit group without the condition ie.:

Lua:
local unitGroup = GetUnitsInRangeOfLocAll(500, GetSpellTargetLoc())

But then the caster is also a member of the unit group thus being affected by the following code which is why I want the condition. This is my first attempt ever at lua (or jass) and I had a different idea where I loop through all the units instead of using GetUnitsInRangeOfLocAll/Matching() but Id like to know why my code doesnt work and I wanna know how to make the conditions work (boolexpr is still confusing me).
 
Level 3
Joined
Sep 1, 2009
Messages
37
Ok Ill add this as a separate reply because I am thoroughly bamboozled. After several hours of testing I rewrote the code as the exact copy of what I wrote here switching return GetFilterUnit() ~= caster for return caster ~= getFilterUnit() and it works... I dont know what the fuck happened but Ill use this as an opportunity to ask a different question.

I remember something about local variables needed to be anulled in jass but someone above mentioned that lua uses dynamic types so I dont need to declare and delete right?

Another question more directly related to the code: Can
GroupPickRandomUnit() pick the same unit multiple times or does it eventually pick every unit from the group?
 
Level 39
Joined
Feb 27, 2007
Messages
5,015
Lua variables do not need to be nil'd, but their data still needs to be destroyed/removed/cleaned as in JASS (unless using Lua auto-recycling code).
Can GroupPickRandomUnit() pick the same unit multiple times or does it eventually pick every unit from the group?
It is random, there are no such guarantees:
JASS:
//===========================================================================
// Consider each unit, one at a time, keeping a "current pick".   Once all units
// are considered, this "current pick" will be the resulting random unit.
//
// The chance of picking a given unit over the "current pick" is 1/N, where N is
// the number of units considered thusfar (including the current consideration).
//
function GroupPickRandomUnitEnum takes nothing returns nothing
    set bj_groupRandomConsidered = bj_groupRandomConsidered + 1
    if (GetRandomInt(1,bj_groupRandomConsidered) == 1) then
        set bj_groupRandomCurrentPick = GetEnumUnit()
    endif
endfunction

//===========================================================================
// Picks a random unit from a group.
//
function GroupPickRandomUnit takes group whichGroup returns unit
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    set bj_groupRandomConsidered = 0
    set bj_groupRandomCurrentPick = null
    call ForGroup(whichGroup, function GroupPickRandomUnitEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(whichGroup)
    endif
    return bj_groupRandomCurrentPick
endfunction
 

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,888
JASS:
function GetUnitsInRangeOfLocMatching takes real radius, location whichLocation, boolexpr filter returns group
    local group g = CreateGroup()
    call GroupEnumUnitsInRangeOfLoc(g, whichLocation, radius, filter)
    call DestroyBoolExpr(filter)
    return g
endfunction
As you can see this function in blizzard.j creates a group, enumerates the filtered units within a specific radius, also destroying the boolexpr[/code]
As it's been already said, it evaluates some expression/performs some operations and then returns a boolean for each cases or not, depends on what you want.
Now imagine we have a trigger with this function, we get 4 possibilities.
JASS:
function AFilter takes nothing returns boolean
    return IsUnitType(GetFilterUnit(), UNIT_TYPE_UNDEAD)
endfunction

function TrigAction takes nothing returns nothing
    local group g = GetUnitsInRangeOfLocMatching(600 GetSpellTargetLoc(), Condition(function AFilter))//--->adds all filtered units (that aren't undead) to the group
    local group g = GetUnitsInRangeOfLocMatching(600 GetSpellTargetLoc(), true)//--->adds filtered units to the group
    local group g = GetUnitsInRangeOfLocMatching(600 GetSpellTargetLoc(), null)//--->same as above? presumingly
    local group g = GetUnitsInRangeOfLocMatching(600 GetSpellTargetLoc(), false)//--->doesn't add any units to the group
endfunction


//trigger init etc...
//...
//...
Basically the logic is: runs GetUnitsInRangeOfLocMatching, if there's boolexpr then check it for each enumerating unit (all of those units will attempt to pass the filter) one at a time, evaluating true or false for each unit and then adding or not adding one to the unit group, respectively.
 
Level 3
Joined
Sep 1, 2009
Messages
37
Lua variables do not need to be nil'd, but their data still needs to be destroyed/removed/cleaned as in JASS (unless using Lua auto-recycling code).

It is random, there are no such guarantees:
JASS:
//===========================================================================
// Consider each unit, one at a time, keeping a "current pick".   Once all units
// are considered, this "current pick" will be the resulting random unit.
//
// The chance of picking a given unit over the "current pick" is 1/N, where N is
// the number of units considered thusfar (including the current consideration).
//
function GroupPickRandomUnitEnum takes nothing returns nothing
    set bj_groupRandomConsidered = bj_groupRandomConsidered + 1
    if (GetRandomInt(1,bj_groupRandomConsidered) == 1) then
        set bj_groupRandomCurrentPick = GetEnumUnit()
    endif
endfunction

//===========================================================================
// Picks a random unit from a group.
//
function GroupPickRandomUnit takes group whichGroup returns unit
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    set bj_groupRandomConsidered = 0
    set bj_groupRandomCurrentPick = null
    call ForGroup(whichGroup, function GroupPickRandomUnitEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(whichGroup)
    endif
    return bj_groupRandomCurrentPick
endfunction

Thank you. Are you getting your documentation from here lep/jassdoc? Is there a way to see how the natives work?
 
Level 3
Joined
Sep 1, 2009
Messages
37
Ok nice so there is no way to check the natives?

Take
vJASS:
function ForGroupBJ takes group whichGroup, code callback returns nothing
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    call ForGroup(whichGroup, callback)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(whichGroup)
    endif
endfunction

it calls ForGroup native which I dont know what it looks like. Another thing is the wantDestroy. How does that work? bj_wantDestroyGroup is a constant set to false by default so if I call ForGroupBJ and want to destroy the group afterwards I should set bj_wantDestroyGroup = true beforehand? But this leads me to another question - does ForGroup also have the code for destroying group? Also DestroyGroup is another native which I dont know how it works, isnt it enough to just set the group you want destroyed to group = nill? Also what is the point of all the BJ functions if they just call native, isnt it redundant? Then there is boolexpr, since I decided to learn lua with no prior experience of jass I didnt realize it "takes nothing" but then how are you supposed to pass variables to it without making them global? Would this work? I put together sth to demonstrate:
Lua:
function someFunc()
    caster = GetTriggerUnit()
    loc = GetSpellTargetLoc()
    group = GetUnitsInRangeOfLocMatching(300, loc, Condition(function()
        return checkConditions(caster)
    end))
end

function checkConditions(unit)
    if unit ~= GetFilterUnit() and BlzGetUnitAbilityCooldownRemaining(whichUnit, abilId) > 0 then
        return true
    end
    return false
end

Im pretty sure this doesnt work because its supposed to take nothing but im passing it caster variable:
Code:
group = GetUnitsInRangeOfLocMatching(300, loc, Condition(checkConditions(caster)))
 
Last edited:

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,888
Ok nice so there is no way to check the natives?
Are(n't) they the same anyone can find from ...\Documents\Warcraft III\JassHelper\ ?
Take
vJASS:
function ForGroupBJ takes group whichGroup, code callback returns nothing
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    call ForGroup(whichGroup, callback)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(whichGroup)
    endif
endfunction

it calls ForGroup native which I dont know what it looks like. Another thing is the wantDestroy. How does that work? bj_wantDestroyGroup is a constant set to false by default so if I call ForGroupBJ and want to destroy the group afterwards I should set bj_wantDestroyGroup = true beforehand? But this leads me to another question - does ForGroup also have the code for destroying group? Also DestroyGroup is another native which I dont know how it works, isnt it enough to just set the group you want destroyed to group = nill? Also what is the point of all the BJ functions if they just call native, isnt it redundant? Then there is boolexpr, since I decided to learn lua with no prior experience of jass I didnt realize it "takes nothing" but then how are you supposed to pass variables to it without making them global? Would this work? I put together sth to demonstrate:
Lua:
function someFunc()
    caster = GetTriggerUnit()
    loc = GetSpellTargetLoc()
    group = GetUnitsInRangeOfLocMatching(300, loc, Condition(function()
        return checkConditions(caster)
    end))
end

function checkConditions(unit)
    if unit ~= GetFilterUnit() and BlzGetUnitAbilityCooldownRemaining(whichUnit, abilId) > 0 then
        return true
    end
    return false
end
ForGroup is a native, that's all there is to it, you can't see what it looks like apart from being written native ForGroup takes group whichGroup, code callback returns nothing
bj_wantDestroyGroup isn't a constant, if it was then you couldn't change its value. Yes you set bj_wantDestroyGroup = true before the ForGroup so it destroys it after executing.
The ForGroup native doesn't have that option to destroy the group, if it had then why would they make ForGroupBJ with that option?
DestroyGroup destroys the group, setting a variable to null/nil in jass/lua just clears the reference of the group in this case.
True, a lot of BJs are redundant because they're mostly warpers to GUI, you see their arguments swapped so that in GUI it can make a proper sentence.
In your trigger, you simply don't because lua can use dynamic functions, and local variables are inside the scope, in this case function someFunc()
Lua:
function someFunc()
    caster = GetTriggerUnit()
    loc = GetSpellTargetLoc()
    group = GetUnitsInRangeOfLocMatching(300, loc, Condition(function()
         return caster ~= GetFilterUnit() and BlzGetUnitAbilityCooldownRemaining(caster, 'A000') > 0
    end))
end
if you don't want to use dynamic functions then simply do:
Lua:
function myCond()
    return GetTriggerUnit()~= GetFilterUnit() and BlzGetUnitAbilityCooldownRemaining(GetTriggerUnit(), 'A000') > 0
end
function someFunc()
    caster = GetTriggerUnit()
    loc = GetSpellTargetLoc()
    group = GetUnitsInRangeOfLocMatching(300, loc, Condition(myCond))
end
 
Level 3
Joined
Sep 1, 2009
Messages
37
ForGroup is a native, that's all there is to it, you can't see what it looks like apart from being written native ForGroup takes group whichGroup, code callback returns nothing
bj_wantDestroyGroup isn't a constant, if it was then you couldn't change its value. Yes you set bj_wantDestroyGroup = true before the ForGroup so it destroys it after executing.
The ForGroup native doesn't have that option to destroy the group, if it had then why would they make ForGroupBJ with that option?
DestroyGroup destroys the group, setting a variable to null/nil in jass/lua just clears the reference of the group in this case.
True, a lot of BJs are redundant because they're mostly warpers to GUI, you see their arguments swapped so that in GUI it can make a proper sentence.
In your trigger, you simply don't because lua can use dynamic functions, and local variables are inside the scope, in this case function someFunc()
Lua:
function someFunc()
    caster = GetTriggerUnit()
    loc = GetSpellTargetLoc()
    group = GetUnitsInRangeOfLocMatching(300, loc, Condition(function()
         return caster ~= GetFilterUnit() and BlzGetUnitAbilityCooldownRemaining(caster, 'A000') > 0
    end))
end
if you don't want to use dynamic functions then simply do:
Lua:
function myCond()
    return GetTriggerUnit()~= GetFilterUnit() and BlzGetUnitAbilityCooldownRemaining(GetTriggerUnit(), 'A000') > 0
end
function someFunc()
    caster = GetTriggerUnit()
    loc = GetSpellTargetLoc()
    group = GetUnitsInRangeOfLocMatching(300, loc, Condition(myCond))
end

Yeah I was using the dynamic version but my question was what if I want to pass an argument to the boolexpr function and the boolexpr is outside of the scope of the scope. Also in your example shouldnt:
Lua:
group = GetUnitsInRangeOfLocMatching(300, loc, Condition(myCond))
be
Lua:
group = GetUnitsInRangeOfLocMatching(300, loc, Condition(myCond()))

So location and group variables are only references and the group and location still exist in the memory even if the reference is gone? Also if I destroyGroup() do I still nil the variable or is that redundant? Actually why would I ever need to nil anything if its a local variable, arent those discarded at the end of the function?
 
Last edited:

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,888
Yeah I was using the dynamic version but my question was what if I want to pass an argument to the boolexpr function and the boolexpr is outside of the scope of the scope.
--->
Im pretty sure this doesnt work because its supposed to take nothing but im passing it caster variable:
Code:
group = GetUnitsInRangeOfLocMatching(300, loc, Condition(checkConditions(caster)))
As you yourself stated and I already posted examples, it doesn't work because a boolexpr takes nothing. So you simply don't pass. That's not how a boolexpr works.

Also in your example shouldnt:
Lua:
group = GetUnitsInRangeOfLocMatching(300, loc, Condition(myCond))
be
Lua:
group = GetUnitsInRangeOfLocMatching(300, loc, Condition(myCond))
What? I don't understand what is the difference that is supposed to be here.

So location and group variables are only references and the group and location still exist in the memory even if the reference is gone? Also if I destroyGroup() do I still nil the variable or is that redundant? Actually why would I ever need to nil anything if its a local variable, arent those discarded at the end of the function?
Memory Leaks
Things That Leak
It's not stricted to those, units, doodads, widgets, pretty much any object you create. In JASS you need to null local variables that are agents, else they suffer a reference leak. Meanwhile in Lua you don't need to do that.
Those links explain you this in more depth, specially the first one.
 
Level 3
Joined
Sep 1, 2009
Messages
37
--->

As you yourself stated and I already posted examples, it doesn't work because a boolexpr takes nothing. So you simply don't pass. That's not how a boolexpr works.


What? I don't understand what is the difference that is supposed to be here.


Memory Leaks
Things That Leak
It's not stricted to those, units, doodads, widgets, pretty much any object you create. In JASS you need to null local variables that are agents, else they suffer a reference leak. Meanwhile in Lua you don't need to do that.
Those links explain you this in more depth, specially the first one.

Lua:
group = GetUnitsInRangeOfLocMatching(300, loc, Condition(myCond))
Lua:
group = GetUnitsInRangeOfLocMatching(300, loc, Condition(myCond()))

I know that boolexpr takes nothing so I was asking how to pass it something and asked if my solution from previous post works.

Also what version of lua does warcraft support? Does it support % as modulo?
 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
5,015
() appended the function name calls a function and because you are giving that output to Condition it expects that value to be a function pointer. If your called function returned a function pointer then yes it would work, but that’s not what you ultimately want to do. You just want to refer to the function directly since you know its name. Hence no () as was determined earlier in the thread.
 
Level 3
Joined
Sep 1, 2009
Messages
37
() appended the function name calls a function and because you are giving that output to Condition it expects that value to be a function pointer. If your called function returned a function pointer then yes it would work, but that’s not what you ultimately want to do. You just want to refer to the function directly since you know its name. Hence no () as was determined earlier in the thread.

Oh ok I think I understand it now, this helped a lot. Essentially: Condition(sth()) calls the function sth() and evaluates its return value then passes it as the argument for Condition() but Condition() doesnt expect its return value as an argument but instead wants to call it as it wills. At the same time GetFilterUnit() used inside of my sth() function probably doesnt exist outside of the scope Condition() function.
 
Last edited:
Level 18
Joined
Nov 21, 2012
Messages
835
To deal with GroupEnum you can:
Lua:
function GroupF()
return GetWidgetLife(GetFilterUnit()) < 100.00
end

local b = Filter(GroupF) -- boolexpr
GroupEnumUnitsInRange(ug, 0.00, 0.00, 600.00, b)

-- or

GroupEnumUnitsInRange(ug, 0.00, 0.00, 600.00, Filter(function()
return GetFilterUnit() == caster
end))

-- or, if you want to pass a parameter:
function GroupF1(caster)
   return GetFilterUnit() == caster
end

GroupEnumUnitsInRange(ug, 0.00, 0.00, 600.00, Filter(function()
    return GroupF1(caster)
end))
Moreover be carefull with comparing handles in Lua. I read that it is not always accurate.
Equality comparisons between handle types are currently somewhat broken and don't work. If you have two "unit" variables that come from different "sources", they may not compare equal. This is a major difference to jass, where equality comparisons work correctly.
Use GetHandleId() to be save.
 
Status
Not open for further replies.
Top