Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
Anyone who knows Jass that would be so kind as to answer my questions have xfire or msn? Messengers are just more convenient than forums imo. My xfire is honeymustard and my msn is [email protected]. You will get huge credit in my map and an e-cookie.
Whats the syntax for 'and' and 'or' for conditions. For example in a condition i need to check if a unit is an enemy of another unit and if the unit is not the same type as another unit would i use &&(and) between the two conditions? Or does Jass have a different symbol for that?
Edit: just typed And in JassCraft and it appears green, im guessing the syntax for and is just and? lol how convenient..
Basically, the "and" and "or" commands take a boolean on each side of them (must be boolean and no other variable), preform their tasks and then return a boolean.
A boolean is the JASS term for a variable with 2 states (your preverbial true/false variable).
A comparison opperation also returns a boolean and so can be used on eithor side of the "and" and "or" commands to return a further boolean.
Brackets also can be mixed in with the comparisons, "and" and "or" opperations. This allows you to preform a group of comparisons before the others (like maths) to get a more desired result.
Alltogether, these should probably be classified as "logical opperations" as they all have to do with comparing booleans to output a single boolean.
Ok, im getting a lot of errors from the compiler, but they don't suggest how to fix. This is my code for making an ability that will fire a missile that damages one target it collides with and then the missile is removed:
Code:
function Trig_Shoot_Conditions takes nothing returns boolean
return GetSpellAbilityId()==’A000’
endfunction
//=============================================================
function Trig_Shoot_Actions takes nothing returns nothing
local real EndDistance
local real CurrentDistance
local location CurrentLoc
local location StartLoc
local unit Bullet
local group G
local real Direction
local player OwnerOfBullet
local boolean Hit
local unit Shooter
set Shooter = GetSpellAbilityUnit()
set CurrentDistance = 0
set EndDistance = 1200
set StartLoc = GetUnitLoc(GetSpellAbilityUnit())
set OwnerOfBullet = GetOwningPlayer(GetSpellAbilityUnit())
set Direction = GetUnitFacing(GetSpellAbilityUnit())
call CreateUnitAtLoc(OwnerOfBullet, 'H000', StartLoc, Direction)
set Bullet = GetLastCreatedUnit()
loop
exitwhen CurrentDistance >= EndDistance
call TriggerSleepAction(.01)
call SetUnitPositionLoc(Bullet, PolarProjectionBJ( GetUnitLoc(Bullet), 20, Direction ))
set CurrentLoc = GetUnitLoc(Bullet)
set CurrentDistance = DistanceBetweenPoints(StartLoc, CurrentLoc)
set G = GetUnitsInRangeOfLocMatching(45, CurrentLoc, (((GetUnitTypeId(Bullet)) != (GetUnitTypeId(GetFilterUnit()))) And (IsUnitEnemy(GetFilterUnit())
if (CountUnitsInGroup(G) >= 1)
then
call UnitDamageTarget(Shooter, FirstOfGroup(G), 100, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
call RemoveUnit(Bullet)
endif
endloop
call RemoveUnit(Bullet)
set Shooter = null
set CurrentDistance = null
set EndDistance = null
set StartLoc = null
set OwnerOfBullet = null
set G = null
set Direction = null
set Bullet = null
set CurrentLoc =null
endfunction
//==== Init Trigger NewTrigger =============================
function InitTrig_Shoot takes nothing returns nothing
set gg_trg_Shoot = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_Shoot, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(gg_trg_Shoot, Condition(function Trig_Shoot_Conditions))
call TriggerAddAction(gg_trg_Shoot, function Trig_Shoot_Actions)
endfunction
function Trig_Shoot_Conditions takes nothing returns boolean
return GetSpellAbilityId()==’A000’
endfunction
//=============================================================
function Trig_Shoot_Actions takes nothing returns nothing
local real EndDistance
local real CurrentDistance
local location CurrentLoc
local location StartLoc
local unit Bullet
local group G
local real Direction
local player OwnerOfBullet
local boolean Hit
local unit Shooter
set Shooter = GetSpellAbilityUnit()
set CurrentDistance = 0
set EndDistance = 1200
set StartLoc = GetUnitLoc(GetSpellAbilityUnit())
set OwnerOfBullet = GetOwningPlayer(GetSpellAbilityUnit())
set Direction = GetUnitFacing(GetSpellAbilityUnit())
call CreateUnitAtLoc(OwnerOfBullet, 'H000', StartLoc, Direction)
set Bullet = GetLastCreatedUnit()
loop
exitwhen CurrentDistance >= EndDistance
call TriggerSleepAction(.01)
call SetUnitPositionLoc(Bullet, PolarProjectionBJ( GetUnitLoc(Bullet), 20, Direction ))
set CurrentLoc = GetUnitLoc(Bullet)
set CurrentDistance = DistanceBetweenPoints(StartLoc, CurrentLoc)
set G = GetUnitsInRangeOfLocMatching(45, CurrentLoc, (((GetUnitTypeId(Bullet)) != (GetUnitTypeId(GetFilterUnit()))) And (IsUnitEnemy(GetFilterUnit())
if (CountUnitsInGroup(G) >= 1)
then
call UnitDamageTarget(Shooter, FirstOfGroup(G), 100, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
call RemoveUnit(Bullet)
endif
endloop
call RemoveUnit(Bullet)
set Shooter = null
set CurrentDistance = null
set EndDistance = null
set StartLoc = null
set OwnerOfBullet = null
set G = null
set Direction = null
set Bullet = null
set CurrentLoc =null
endfunction
//==== Init Trigger NewTrigger =============================
function InitTrig_Shoot takes nothing returns nothing
set gg_trg_Shoot = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_Shoot, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(gg_trg_Shoot, Condition(function Trig_Shoot_Conditions))
call TriggerAddAction(gg_trg_Shoot, function Trig_Shoot_Actions)
endfunction
#2: return GetSpellAbilityId()==’A000’ its ' not `, thus:
#3: Indent your code please... you indent if, else, elseif, loop, function, pretty much every code block.
#4: You can initialize locals like so:
JASS:
local real EndDistance = 1200
local real CurrentDistance = 0
local location CurrentLoc
local location StartLoc = GetUnitLoc(GetSpellAbilityUnit())
local unit Bullet
local group G
local real Direction = GetUnitFacing(GetSpellAbilityUnit())
local player OwnerOfBullet = GetOwningPlayer(GetSpellAbilityUnit())
local boolean Hit
local unit Shooter = GetSpellAbilityUnit()
#5:
JASS:
call CreateUnitAtLoc(OwnerOfBullet, 'H000', StartLoc, Direction)
set Bullet = GetLastCreatedUnit()
#5.1: GetLastCreatedUnit() only works with blizzard.j unit creation functions. that is a native one.
#5.2: CreateUnitAtLoc() returns the created unit.
#5.3: Again, local initialization. Thus:
JASS:
local unit Bullet = CreateUnitAtLoc(OwnerOfBullet, 'H000', StartLoc, Direction)
Because StatLoc is declared before Bullet, we can use it like that.
#6:
JASS:
set Shooter = null
set CurrentDistance = null
set EndDistance = null
set StartLoc = null
set OwnerOfBullet = null
set G = null
set Direction = null
set Bullet = null
set CurrentLoc =null
You only have to null types which extend from the handle type, and you only have to null locals that will contain a handle that will eventually be destroyed. thus:
JASS:
set Shooter = null
set StartLoc = null
set G = null
set Bullet = null
set CurrentLoc = null
So heres what where up to:
JASS:
function Trig_Shoot_Conditions takes nothing returns boolean
return GetSpellAbilityId() == 'A000'
endfunction
//=============================================================
function Trig_Shoot_Actions takes nothing returns nothing
local real EndDistance = 1200
local real CurrentDistance = 0
local location CurrentLoc
local location StartLoc = GetUnitLoc(GetSpellAbilityUnit())
local player OwnerOfBullet = GetOwningPlayer(GetSpellAbilityUnit())
local real Direction = GetUnitFacing(GetSpellAbilityUnit())
local unit Bullet = CreateUnitAtLoc(OwnerOfBullet, 'H000', StartLoc, Direction)
local group G
local boolean Hit
local unit Shooter = GetSpellAbilityUnit()
loop
exitwhen CurrentDistance >= EndDistance
call TriggerSleepAction(.01)
call SetUnitPositionLoc(Bullet, PolarProjectionBJ( GetUnitLoc(Bullet), 20, Direction ))
set CurrentLoc = GetUnitLoc(Bullet)
set CurrentDistance = DistanceBetweenPoints(StartLoc, CurrentLoc)
set G = GetUnitsInRangeOfLocMatching(45, CurrentLoc, (((GetUnitTypeId(Bullet)) != (GetUnitTypeId(GetFilterUnit()))) And (IsUnitEnemy(GetFilterUnit())
if (CountUnitsInGroup(G) >= 1)
then
call UnitDamageTarget(Shooter, FirstOfGroup(G), 100, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
call RemoveUnit(Bullet)
endif
endloop
call RemoveUnit(Bullet)
set Shooter = null
set StartLoc = null
set G = null
set Bullet = null
set CurrentLoc = null
endfunction
#7:
JASS:
if (CountUnitsInGroup(G) >= 1)
then
#7.1: if condition then, all on one line.
#7.2: You don't need the outermost parenthesis. thus:
JASS:
if CountUnitsInGroup(G) >= 1 then
#8: This line is just completely wrong:
JASS:
set G = GetUnitsInRangeOfLocMatching(45, CurrentLoc, (((GetUnitTypeId(Bullet)) != (GetUnitTypeId(GetFilterUnit()))) And (IsUnitEnemy(GetFilterUnit())
#8.1: GetUnitsInRangeOfLocMatching() is a BJ function. (Blizzard.J) BJ's are coded in JASS, whereas natives are coded in C++ within the game. Natives are always preferable, especially considering most blizzard.j functions are so poorly coded.
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
#8.1.1: Don't destroy boolexprs.
#8.1.2: Don't use locations. Use real x and y coordinated.
#8.2: A boolexpr is a pointer to a function that returns a boolean type value. You create a boolexpr with the Condition() or Filter() functions as such: Condition(function YourFunctionName) YourFunctionName is the name of a function which takes nothing and returns a boolean. Thus, ignoring the BJ function so far and just using a proper boolexpr:
JASS:
function Trig_Shoot_Conditions takes nothing returns boolean
return GetSpellAbilityId() == 'A000'
endfunction
function Shoot_Filter takes nothing returns boolean
return GetUnitTypeId(GetFilterUnit()) != 'H000' and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit()))
endfunction
function Trig_Shoot_Actions takes nothing returns nothing
local real EndDistance = 1200
local real CurrentDistance = 0
local location CurrentLoc
local location StartLoc = GetUnitLoc(GetSpellAbilityUnit())
local player OwnerOfBullet = GetOwningPlayer(GetSpellAbilityUnit())
local real Direction = GetUnitFacing(GetSpellAbilityUnit())
local unit Bullet = CreateUnitAtLoc(OwnerOfBullet, 'H000', StartLoc, Direction)
local group G
local boolean Hit
local unit Shooter = GetSpellAbilityUnit()
loop
exitwhen CurrentDistance >= EndDistance
call TriggerSleepAction(.01)
call SetUnitPositionLoc(Bullet, PolarProjectionBJ( GetUnitLoc(Bullet), 20, Direction ))
set CurrentLoc = GetUnitLoc(Bullet)
set CurrentDistance = DistanceBetweenPoints(StartLoc, CurrentLoc)
set G = GetUnitsInRangeOfLocMatching(45, CurrentLoc, Condition(function Shoot_Filter))
if CountUnitsInGroup(G) >= 1 then
call UnitDamageTarget(Shooter, FirstOfGroup(G), 100, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
call RemoveUnit(Bullet)
endif
endloop
call RemoveUnit(Bullet)
set Shooter = null
set StartLoc = null
set G = null
set Bullet = null
set CurrentLoc = null
endfunction
//==== Init Trigger NewTrigger =============================
function InitTrig_Shoot takes nothing returns nothing
set gg_trg_Shoot = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_Shoot, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(gg_trg_Shoot, Condition(function Trig_Shoot_Conditions))
call TriggerAddAction(gg_trg_Shoot, function Trig_Shoot_Actions)
endfunction
#9: You use locations... which you don't have to do. Switching to using X and Y coordinates, and inlining the GetUnitsInRangeOfLocMatching() BJ and a few others, we end up with:
JASS:
function Trig_Shoot_Conditions takes nothing returns boolean
return GetSpellAbilityId() == 'A000'
endfunction
function Shoot_Filter takes nothing returns boolean
return GetUnitTypeId(GetFilterUnit()) != 'H000' and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit()))
endfunction
function Trig_Shoot_Actions takes nothing returns nothing
local real EndDistance = 1200
local real CurrentDistance = 0
local unit Shooter = GetTriggerUnit() // TriggerUnit == GetSpellAbilityUnit()
local player OwnerOfBullet = GetOwningPlayer(Shooter)
local real Direction = GetUnitFacing(Shooter)
local real StartX = GetUnitX(Shooter)
local real StartY = GetUnitY(Shooter)
local real CurrentX
local real CurrentY
local unit Bullet = CreateUnit(OwnerOfBullet, 'H000', StartX, StartY, Direction)
local group G
loop
exitwhen CurrentDistance >= EndDistance
call TriggerSleepAction(.01)
// Polar projection:
set CurrentX = GetUnitX(Bullet) + 20 * Cos(Direction * bj_DEGTORAD)
set CurrentY = GetUnitY(Bullet) + 20 * Sin(Direction * bj_DEGTORAD)
call SetUnitPosition(Bullet, CurrentX, CurrentY)
set CurrentDistance = SquareRoot(CurrentX*StartX + CurrentY*StartY)
set G = CreateGroup()
call GroupEnumUnitsInRange(G, CurrentX, CurrentY, 45, Condition(function Shoot_Filter))
if CountUnitsInGroup(G) >= 1 then
call UnitDamageTarget(Shooter, FirstOfGroup(G), 100, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
call RemoveUnit(Bullet)
endif
endloop
call RemoveUnit(Bullet)
set Shooter = null
set G = null
set Bullet = null
endfunction
function InitTrig_Shoot takes nothing returns nothing
set gg_trg_Shoot = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_Shoot, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(gg_trg_Shoot, Condition(function Trig_Shoot_Conditions))
call TriggerAddAction(gg_trg_Shoot, function Trig_Shoot_Actions)
endfunction
#10: if CountUnitsInGroup(G) >= 1 then CountUnitsInGroup() is a BJ. a not completely useless one, however: FirstOfGroup() will return null if a group is empty, so:
JASS:
if FirstOfGroup(G) != null then
#11:
JASS:
if FirstOfGroup(G) != null then
call UnitDamageTarget(Shooter, FirstOfGroup(G), 100, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
call RemoveUnit(Bullet)
endif
A minor logic error: If the bullet is removed before its out of distance, then the loop will continue iterating. Adding an exitwhen true fixes that. Also, if you're editing the loop, you don't need to remove the unit here, as its done after the loop anyway.
So, heres what we have:
JASS:
function Trig_Shoot_Conditions takes nothing returns boolean
return GetSpellAbilityId() == 'A000'
endfunction
function Trig_Shoot_Filter takes nothing returns boolean
return GetUnitTypeId(GetFilterUnit()) != 'H000' and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit()))
endfunction
function Trig_Shoot_Actions takes nothing returns nothing
local unit Shooter = GetTriggerUnit() // TriggerUnit == GetSpellAbilityUnit()
local real Direction = GetUnitFacing(Shooter)
local real StartX = GetUnitX(Shooter)
local real StartY = GetUnitY(Shooter)
local real CurrentX
local real CurrentY
local real CurrentDistance = 0
local real EndDistance = 1200
local unit Bullet = CreateUnit(GetOwningPlayer(Shooter), 'H000', StartX, StartY, Direction)
local group G
loop
exitwhen CurrentDistance >= EndDistance
call TriggerSleepAction(.01)
// Polar projection:
set CurrentX = GetUnitX(Bullet) + 20 * Cos(Direction * bj_DEGTORAD)
set CurrentY = GetUnitY(Bullet) + 20 * Sin(Direction * bj_DEGTORAD)
call SetUnitPosition(Bullet, CurrentX, CurrentY)
set CurrentDistance = SquareRoot(CurrentX*StartX + CurrentY*StartY)
set G = CreateGroup()
call GroupEnumUnitsInRange(G, CurrentX, CurrentY, 45, Condition(function Shoot_Filter))
if FirstOfGroup(G) != null then
call UnitDamageTarget(Shooter, FirstOfGroup(G), 100, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
exitwhen true
endif
endloop
call RemoveUnit(Bullet)
set Shooter = null
set G = null
set Bullet = null
endfunction
function InitTrig_Shoot takes nothing returns nothing
set gg_trg_Shoot = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_Shoot, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(gg_trg_Shoot, Condition(function Trig_Shoot_Conditions))
call TriggerAddAction(gg_trg_Shoot, function Trig_Shoot_Actions)
endfunction
Theres more problems with this code that I'm too lazy to go over right now:
It uses TriggerSleepAction() instead of a timer
The exitwhen it based on a distance between points calculation, whereas it could be optimized to work on a static number of iterations. (As you're always moving at most 1500 units, you can just iterate 1500/20 times.)
There are constant values like the speed of 20 and unit rawcodes all over. You can use constant functions which only return a value to get them all in one place. eg:
JASS:
constant function Shoot_GetBulletSpeed takes nothing returns real
return 20
endfunction
Hope this helps...
Edit: Oh, and if you have any JASS questions and want some real-time help, drop by the chat room: http://www.hiveworkshop.com/resources_new/chat/ the link to it is also at the top of every THW page
Wow thanks for going over my code, i appreciate it -- you taught me a lot i needed to know. Ill be sure to read a tut on timers and yeah i know there's a lot of raw values but I was more concerned getting things to work at the time. Thanks again!
Ok this is my code for the spell as of now, the biggest problem is with the calling the TimerStart, it wont let me use any arguments for that function in the timer start, whats my alternative for making this work?
JASS:
function Trig_Shoot_Conditions takes nothing returns boolean
return GetSpellAbilityId() == 'A000'
endfunction
function Trig_Shoot_Filter takes nothing returns boolean
return GetUnitTypeId(GetFilterUnit()) != 'u000' and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit()))
endfunction
function Trig_Shoot_Unit takes unit bullet, unit shooter returns nothing
local real Direction = GetUnitFacing(bullet)
local real Direction = GetUnitFacing(bullet)
local real CurrentX = GetUnitX(bullet) + 60 * Cos(Direction * bj_DEGTORAD)
local real CurrentY = GetUnitY(bullet) + 60 * Sin(Direction * bj_DEGTORAD)
local group G = CreateGroup()
if GetWidgetLife(bullet) > 0.405 then
call SetUnitPosition(bullet, CurrentX, CurrentY)
call GroupEnumUnitsInRange(G, CurrentX, CurrentY, 45, Condition(function Trig_Shoot_Filter))
if FirstOfGroup(G) != null then
call UnitDamageTarget(shooter, FirstOfGroup(G), 700, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
endif
endif
set G = null
endfunction
function Trig_Shoot_Actions takes nothing returns nothing
local unit Shooter = GetTriggerUnit()
local real Direction = GetUnitFacing(Shooter)
local real StartX = GetUnitX(Shooter)
local real StartY = GetUnitY(Shooter)
local unit Bullet = CreateUnit(GetOwningPlayer(Shooter), 'u000', StartX, StartY, Direction)
local timer t
call TimerStart(t, .035, true, function Trig_Shoot_Unit(Bullet, Shooter))
call TriggerSleepAction(.5)
call DestroyTimer(t)
call KillUnit(Bullet)
set Shooter = null
set Bullet = null
set t = null
endfunction
function InitTrig_Shoot takes nothing returns nothing
set gg_trg_Shoot = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_Shoot, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(gg_trg_Shoot, Condition(function Trig_Shoot_Conditions))
call TriggerAddAction(gg_trg_Shoot, function Trig_Shoot_Actions)
endfunction
You can not pass argumants to a function run in a timer directly. This is quite logical since it clearly says it wants a function name rather than a call of some kind. Thus you need to store the arguments you want to pass to the timers in a variable of some kind so that they can be axcessed when the timer wants them.
However this results in a problem for multyinstantcastibility and so a system needs to be made that from the timer object, it can get the arguments you passed through that specific timer and so not collide with arguments passed with other timers.
An easy way to avioid systems like PurplePoot wanted to use and remain realtivly efficent is the array loop method. Basically it can be easy to program at the time but might not cut it as far as efficency goes if there are a lot of ocjects stored.
Below is a vJASS spell I made that uses that system.
JASS:
library Attract initializer int requires TimerBank
globals
private constant integer ACTIVATOR_SPELL = 'A005'
private constant integer EFFECTED_SPELL = 'A004'
private unit array units
private timer array timers
private integer n = 0
public trigger MAIN = CreateTrigger()
endglobals
private function condition takes nothing returns boolean
return GetSpellAbilityId() == ACTIVATOR_SPELL
endfunction
private function end takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer i = 0
loop
exitwhen timers[i] == t
set i=i+1
endloop
call SetUnitAbilityLevel(units[i],EFFECTED_SPELL,1)
call TimerBank_Deposit(t)
set n=n-1
set timers[i] = timers[n]
set units[i] = units[n]
set timers[n] = null
set units[n] = null
set t = null
endfunction
private function action takes nothing returns nothing
local unit u = GetSpellAbilityUnit()
set units[n] = u
set timers[n] = TimerBank_Withdraw()
call TimerStart(timers[n],15,false,function end)
set n=n+1
call SetUnitAbilityLevel(u,EFFECTED_SPELL,GetUnitAbilityLevel(u,ACTIVATOR_SPELL)+1)
set u = null
endfunction
private function int takes nothing returns nothing
call TriggerAddAction(MAIN,function action)
call TriggerAddCondition(MAIN,Condition(function condition))
call TriggerRegisterAnyUnitEventBJ(MAIN,EVENT_PLAYER_UNIT_SPELL_EFFECT)
endfunction
endlibrary
A private function just is a function that can't be accessed outside of a scope.
When in a scope, it adds a prefix (usually the scope name I think) and thus, will allow it to be inaccessable outside of the scope. So you can reuse simple function names such as init, actions, conditions, etc.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.