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

"Expected a function name" and some question

Status
Not open for further replies.
Level 4
Joined
Feb 12, 2016
Messages
71
Hello,

i am very new to writing JASS Code, though being a Software Developer familiar with the genre itself.

I am now trying to write some simple custom spells, using JassCraft and GUI -> Jass Translation as base of finding the functions that i need to implement desired functionality.

So far its working quite well, im getting a hang of the naming conventions in order to guess function names that do what i am looking for (i search for what i think certain functions should be called in JassCraft and check the function protoype).

But now i am encountering some kind of syntax error that i cant seem to get behind, here is the code:

JASS:
function Trig_Arcane_Blast_IsEnemyUnit takes nothing returns boolean
   return ( IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), GetOwningPlayer(GetTriggerUnit())) == true) 
endfunction


function Trig_Arcane_Blast_Actions takes nothing returns nothing
    local group enemyUnits = GetUnitsInRangeOfLocMatching(250, GetSpellTargetLoc(), Condition(function Trig_Arcane_Blast_IsEnemyUnit))
    call DamageUnitGroup(GetOwningPlayer(GetTriggerUnit()), 3 * GetHeroInt(GetTriggerUnit(), true), enemyUnits)
    set enemyUnits = null
endfunction

I am getting an error message, stating "Expecting function name" for the line in which i call DamageUnitGroup.

I have looked up the prototype of this function in JassCraft and i pass parameters matching this - i cant really seem to understand what is wrong here.

When i parse the entire thing in JassCraft, there seem to be no errors. So it seems, that the WorldEditor must have some kind of different definition of DamageUnitGroup than what JassCraft tells me?

That or i simply have some different syntax-error that i cannot see, being really new to this script language.

2. Another Question: Is the way i approach finding functions the "best practice" or is there a smarter way of doing it?
I am really used to developing in well documented languages / APIs, where i can easily lookup stuff, say like MSDN, which, understandably, is not the case for Jass.

Thanks in advance :)
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
Did you post the whole code? I don't see the function for DamageUnitGroup anywhere.

Another Question: Is the way i approach finding functions the "best practice" or is there a smarter way of doing it?
I am really used to developing in well documented languages / APIs, where i can easily lookup stuff, say like MSDN, which, understandably, is not the case for Jass.

If you download JNGP 2.0, it comes with TESH which has an easy to use function list.
 
Level 4
Joined
Feb 12, 2016
Messages
71
Eh ... I found DamageUnitGroup in the list that jasscraft offers, I had assumed that list are those functions provided Jass?

Is DamageUnitGroup not a "standard" function?

Why is it in jasscraft if it is not?

I will definitely check out what you posted as resource too, thank you!
 
Level 4
Joined
Feb 12, 2016
Messages
71
I have attached a screenshot from JassCraft.

I have downloaded this tool as it was recommended as good tool to support with Jass-development in various sources.

It says "native list", which i had assumed is a list of functions that the World Edtor / JASS natively provides?
 

Attachments

  • JassCraft_screen.png
    JassCraft_screen.png
    132.4 KB · Views: 100
Level 4
Joined
Feb 12, 2016
Messages
71
Pug, I reall don't know.

I assumed that due to the game being that old the code standard would be unchanged.

If Jass craft shows some weird special functions that don't even exist then it's quite useless for me because I only get miss lead.

What's the deal with such special functions?

I only found this damage unit group to be neat because I avoid using ForGroup and declaring another function to pass into it

But if it doesn't exist, then well, I guess I just go the standard way :D
 
Level 12
Joined
Jan 2, 2016
Messages
973
native.j and blizzard.j
These sites help me a lot for learning JASS.
You only have to look at the functions where they do the looping. The important part for you is:
JASS:
    local unit FoG=null //We initially declare an empty unit handler
    call GroupEnumUnitsInRange(grp,0.,0.,256.,null) //Note that we're using 'null' for our filterfunc. This means that *all* units in range will be added to the group.
    loop
        set FoG=FirstOfGroup(grp) //If you're new to this kind of loop, this part might look strange to you.
        exitwhen FoG==null //combined with the above line, this is effectively checking if the group is empty
        if GetUnitTypeId(FoG)=='hfoo' then //here we have a make-shift condition statement.. followed by an action below
            call KillUnit(FoG)
        endif
        call GroupRemoveUnit(grp,FoG) //here's how we make the loop eventually terminate. Remove first of group, and when the loop restarts we get the new "first of group".
    endloop

Basically, you use this function called FirstOfGroup() which will grab an arbitrary unit from the group (there's no way you can safely determine which one it picks first, but it doesn't matter).

If it gets null, that means it didn't find a unit (there are some edge cases where this is a bug you have to deal with, but we'll ignore that for now). Anyway, you exit the loop if you found null for FirstOfGroup(). This is how you know when to exit the loop.

Then you do your normal loop actions. As an example, the tutorial checks if it's a footman and then kills it if it is.

At the bottom of the loop, you must remove the unit from the group. This is so that you don't infinitely loop on the same unit. However, as I mentioned in my post, it will make the group empty by the end.

What's important is that you don't need to create and destroy the group each time. You create a global group variable and you use that for these loops.

Anyway, since this whole loop is inside one function, you can use local variables inside it. It is also faster for various reasons than ForGroup(...).

And that's the way to do it without "ForGroup"
 
Level 4
Joined
Feb 12, 2016
Messages
71
Is ForGroup really that bad?

Because this looks a little out of the way, especially when it mentions some sort of bug.

Thanks for the resources though, greatly appreciated!
 
Level 12
Joined
Jan 2, 2016
Messages
973
I'm not sure what kind of bug was SAUS talking about (I haven't experienced any), but everyone seems to be relying on FirstOfGroup method. It's faster, and you can use local variables with it.
The only drwaback is that you clear the unit group. But that's not so bad..
When I need to "keep" the unit group I do call GroupAddUnit(r, FoG) call GroupRemoveUnit(g, FoG)
and then I just use r instead of g :p

Example trigger:
JASS:
function Paralising_Howl takes nothing returns boolean
    local unit c
    local group g
    local unit u
    local player p
    local group r
    local real count
    if GetSpellAbilityId() == 'A01Q' then
        set c = GetTriggerUnit()
        set g = CreateGroup()
        set r = CreateGroup()
        set p = GetOwningPlayer(c)
        set count = 0.00
        call GroupEnumUnitsInRange(g, GetUnitX(c), GetUnitY(c), 240.00+(25.00*I2R(GetUnitAbilityLevel(c,'A01Q'))), null)
        loop
            set u = FirstOfGroup(g)
            exitwhen u == null
            if IsUnitType(u, UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_MECHANICAL) or IsUnitAlly(u, p) then
            else
                set count = count + 1
                call GroupAddUnit(r, u)
            endif
            call GroupRemoveUnit(g, u)
        endloop
        call DestroyGroup(g)
        set g = null
        loop
            set u = FirstOfGroup(r)
            exitwhen u == null
            call UnitDamageTargetBJ( c, u, count*(5.00+(5.00*I2R(GetUnitAbilityLevel(c,'A01Q')))), ATTACK_TYPE_MELEE, DAMAGE_TYPE_NORMAL )
            call GroupRemoveUnit(r, u)
        endloop
        call DestroyGroup(r)
        set r = null
        set p = null
        set c = null
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Paralising_Howl takes nothing returns nothing
    set gg_trg_Paralising_Howl = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Paralising_Howl, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Paralising_Howl, Condition( function Paralising_Howl ) )
endfunction
Tho.. in this trigger I'm re-adding only the units I need, not 'all', but you can re-add all units if you want (by taking the GroupAddUnit out of the "if")
 
Level 3
Joined
Apr 17, 2008
Messages
41
Your JassCraft is checking a file called CASTER~1.j along with blizzard.j and common.j, but Warcraft III only knows the functions in common.j and blizzard.j. Find CASTER~1.j and remove it (or copy and paste the file's contents into your map script if you want to use the functions in that file).

DamageUnitGroup, DamageUnitGroupEx, and GetACaster don't exist in blizzard.j or common.j.
 
Level 4
Joined
Feb 12, 2016
Messages
71
@WereElf
Yep, it's pretty straight forward and i understand the approach! It's basically the non-syntax sugar version of a usualy forEach loop in whatever programming language.

If you say that this is the preffered way of looping through groups then I shall do it this way :)


@Sesamia
Very interesting, that would explain why these functions show up.

I have looked into the JassCraft folder and found a file that is somewhat similar to what you mentioned, its called CasterSystem.j.

In the settings-file of jasscraft i put the entry [JassUse] CasterSystem.j=1 to 0 and now the function doesnt show up anymore.

Thanks a lot for your input!


Edit: Some more questions that wander through my mind:

Say, when i have a trigger that goes something like this (pseudocode):
- Some Event happens
- Create a Unit
- Add ability to (Last created unit)

To me, this feels like it would be unsafe in Multiplayer. What if, between the steps of create and add ability to last created, someone else creates a unit - then the add ability to last created unit would target the wrong unit, wouldnt it?

Same goes for GetTriggerUnit(). Is this guaranteed to be held locally for each trigger or can other triggers interfer with it? Say my Trigger A goes off and instantly after that Trigger B also triggers, now do i reference the triggering unit of Trigger B when i use GetTriggerUnit() in Trigger A?
 
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, here comes some explaining:
1) Triggers are queued, they don't run simutaneously, thus another trigger can't replace the "Last Created Unit" UNLESS you wave a "wait" (trigger sleep action/ polled wait/ etc)
2) Triggering unit is a "local variable" and it's ALWAYS pointing at the same unit, even after a wait. Be careful tho, cuz "Triggering unit" is the only local native. "Casting unit" (f. ex) gets replaced :p
3) You should be using local variables to avoid a variable's value being replaced during the wait.
You can create units like this:
JASS:
local unit u = GetTriggerUnit()
local unit tar = GetSpellTargetUnit()
local real x = GetUnitX(tar)
local real y = GetUnitY(tar)
local unit d = CreateUnit(GetOwningPlayer(u), '0000', x, y, 0.00)
and after that you can refer to the "last created unit" with "d" instead, and it will always be pointing towards that unit, (within this trigger, and as long as you don't set d's value to something else).

EDIT:
4) You should avoid using Waits in general, use timers instead. For them you need a hashtable tho. Create one hashtable in the game initialization, and set a hashtable global variable's value to the last created hashtable. After that you can do this, when you want more actions to happen after time:
JASS:
function TimerExpired takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
/////////// EXAMPLE ACTION ////////////
    call PauseUnit(LoadUnitHandle(udg_Table, id, 'unit'), false)
////////////////////////////////////////////
    call FlushChildHashtable(udg_Table, id)
    call DestroyTimer(t)
    set t = null
endfunction

function SampleFunction takes nothign returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)
//////////// EXAMPLE ACTIONS /////////////
    local unit u = GetTriggerUnit()
    call PauseUnit(u, true)
    call SaveUnitHandle(udg_Table, id, 'unit', u)
    set u = null
////////////////////////////////////////////////
    call TimerStart(t, 2.00, false, function TimerExpired) // 2.00 is the time you want the timer to last
    set t = null
endfunction

Here is a more realistic example (from a spell I'm using)
JASS:
function PlayCastAnimation takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u
    local integer id = GetHandleId(t)
    local integer i = LoadInteger(udg_Table, id, 'loop')
    if i > 20 then
        call RecTimer(t)
    else
        set u = LoadUnitHandle(udg_Table, id, 'unit')
        if i >=1 then
            call QueueUnitAnimation( u , "spell")
        else
            call SetUnitAnimation(u, "spell")
        endif
        set u = null
        call SaveInteger(udg_Table, id , 'loop', i + 1)
    endif
    set t = null
endfunction

function SEF_Begining takes nothing returns boolean
    local trigger tr = GetTriggeringTrigger()
    local unit c = LoadUnitHandle(udg_Table, GetHandleId(tr), 'unit')
    local unit u = GetTriggerUnit()
    local integer id
    local timer t
    if u == c then
        call UnitApplyTimedLife(LoadUnitHandle(udg_Table, GetHandleId(tr), 'dumy'), 'BTLF', 0.01)
        call FlushChildHashtable(udg_Table, GetHandleId(tr))
        set id = GetHandleId(u)
        call SetUnitFacing(u, LoadReal(udg_Unit_Table, id, 'msta')*bj_RADTODEG)
        set t = GetFreeTimer()
        call SaveUnitHandle(udg_Table, GetHandleId(t), 'unit', u)
        call TimerStart(t, 0.50, true, function PlayCastAnimation)
        set t = null
        call PauseUnit(u, true)
        call DestroyTrigger(tr)
    endif
    set tr = null
    set c = null
    set u = null
    return false
endfunction
Do have in mind that "GetFreeTimer()" and "RecTimer(t)" are custom functions, that I have in my map's header. You should use "CreateTimer()" and DestroyTimer(t) instead (until you have recycling functions). I don't clear t's hashtable, because "RecTimer(t)" does that for me, but you should be clearing it.
 
Last edited:
Level 4
Joined
Feb 12, 2016
Messages
71
Nice, thanks for clearing that up!

Timer example also comes in really handy, as i will surely need at least a few Timer in the Map!
When the time comes i can use your code for a good reference :)
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
@WereElf & SAUS,
Unit groups are vectors, you add units to the edge of the list (I assume the front because of the following), FirstOfGroup() returns the unit last added to the group... so the first of the list, units removed from the group are removed from the front as well.

@Sesamia & Mercious,
You can import custom code snippets into the import manager, or you can import it via the preprocessor that is included in JNGP. (I forgot/never cared about the name of that call though.)

@Mercious & WereElf,
Trigger executions can also be nested if another trigger runs because the first trigger does something that triggers the events of the second.
For example, if I have a trigger that creates a unit, then I have another trigger that runs on the event when a unit is created and it creates another unit, then last created unit will not be the right value.

Also, (very intuitive,) triggers run synchrome with the game, so when you have a super heavy trigger that takes 100 seconds to do its actions (either really bad pc or seriously heavy stuff), and just before that trigger ran, you started training a unit that takes 1 second to train... the unit will still be trained after that trigger is finished.
The game's clock (which makes units move, train, fight, etc) will not continue before the trigger is finished.
If the trigger is in a wait, it will continue running after a check if the wait is complete.
(TriggerSleepAction() that is, PolledWait() technically does that check twice... for each TSA.)

You can also use TimerUtils for timers so you can give them an id so you can use arrays instead of hashtables (you should if you care about super performance... for example when you do heavy stuff).
Also, keep in mind that 'unit' and stuff like that only work with 4 characters, because they are ascii parsed and JASS doesnt take in other lengths.
 
Level 7
Joined
Oct 19, 2015
Messages
286
Let's see if I can clear some things up.
1) Triggers are queued, they don't run simutaneously, thus another trigger can't replace the "Last Created Unit" UNLESS you wave a "wait" (trigger sleep action/ polled wait/ etc)
As Wietlol explained, this is incorrect:
Wietlol said:
Trigger executions can also be nested if another trigger runs because the first trigger does something that triggers the events of the second.
For example, if I have a trigger that creates a unit, then I have another trigger that runs on the event when a unit is created and it creates another unit, then last created unit will not be the right value.
In general this is true, but the example is incorrect. There is no "a unit is created" event. The closest thing we have is "unit enters region" and that particular event behaves differently, the trigger conditions/actions get queued instead of nested like they do with other events (this used to cause all sorts of trouble for indexing systems until it was discovered that the event filter does get nested).

2) Triggering unit is a "local variable" and it's ALWAYS pointing at the same unit, even after a wait. Be careful tho, cuz "Triggering unit" is the only local native. "Casting unit" (f. ex) gets replaced :p
Actually, as far as I remember "casting unit" (and other cast event responses) are the exception (they were added in a later patch). Most event responses behave like "triggering unit" and remain correct after waits...

...but you shouldn't really be using waits anyway.
 
Status
Not open for further replies.
Top