Aliases

Megalo only supports a limited number of variables; however, you can define aliases, or custom names, for each variable. Once defined, an alias can be used in any context where the original variable could be used.

alias is_zombie = player.number[0]

for each player do
   if current_player.is_zombie == 1 then
      --
      -- ...
      --
   end
end

You can also alias certain values that are specific to argument types, such as object types:

alias temp_int_00   = global.number[0]
alias objective_obj = flag

for each team do
   alias created = temp_int_00
   --
   created = 0
   for each object with label "ctf_return" do
      if created == 0 and current_object.team == current_team then
         current_object.place_at_me(objective_obj, none, never_garbage_collect, 0, 0, 0, none)
         created = 1
      end
   end
end

Features of aliases

Absolute or relative

Aliases can be "absolute" or "relative." An absolute alias refers to an integer constant or to a global value, while a relative alias refers to a nested variable.

alias absolute_constant = 5

alias absolute_variable = global.number[0]

if absolute_variable == 1 then
   -- ...
end

--
-- Relative alias:
--

alias relative_variable = player.number[0]

for each player do
   if current_player.relative_variable == 1 then
      -- ...
   end
end

In that code example, we define relative_variable as an alias that can be accessed on all player-type variables; the alias refers to any given player's nested number[0] variable.

Scoping

Aliases are block-scoped. This means that they only exist inside of the block of code they were defined in; they go out of scope — they stop existing — past the end of that block.

-- Let's mark a variable for temporary use, with an alias.
alias temporary_number_variable = global.number[0]

for each player do
   if current_player.killer_type_is(kill) then
      alias death_type = temporary_number_variable
      
      death_type = current_player.get_death_damage_type()
      if death_type == enums.damage_reporting_type.dmr then
         --
         -- (current_player) was killed with a DMR.
         --
      end
   end
end

-- The "death_type" alias isn't available here, so it doesn't make a mess.

Aliases that are defined outside of all blocks will never go out of scope; they are always in-scope — always available from the current scope.

Shadowing

Aliases shadow each other. That is: if you define multiple aliases with the same name, the most recently defined one (i.e. the one furthest down in the code) will prevent access to the others.

global.number[0] = 3

alias example_alias = 1
alias example_alias = 2
alias example_alias = 3 -- This alias shadows the others...

if global.number[0] == example_alias then
   -- ...so code in this block will run.
end

Shadowing is affected by scoping:

alias example = 1

for each player do
   alias example = 5 -- Shadows the other alias.
   
   current_player.number[0] = example
end
-- The "shadowing" alias isn't in-scope anymore. The alias that was once shadowed 
-- is now available: `example` is now `1`.

Allocation to aliases

Variables can be allocated to aliases dynamically. That is: you can ask for a variable of a given type, without having to manually specify the variable's index.

alias absolute_alias = allocate global.number

alias relative_alias = allocate player.number

For these alias declarations, ReachVariantTool will look for any variable of the requested type that is not already pointed to by an alias that is in-scope, and use the first such variable that it finds. If no such variables remain (i.e. every variable of the type you've asked for is already aliased), then a compiler error will occur.

alias some_alias = global.number[0]

-- The `global.number[0]` variable is referred to by another alias, so while 
-- there's no guarantee that `example` will refer to any specific global.number 
-- variable, there *is* a guarantee that it *won't* refer to `global.number[0]`.
alias example  = allocate global.number

You can also allocate temporary variables using a special syntax:

alias my_temporary_variable = allocate temporary number

Collision warnings

If you accidentally point an alias at a variable that was allocated to another alias, the compiler will warn you.

-- Assume this is the only code in the entire script.

alias example_allocated = allocate global.number
-- Because no other global.number variables have been aliased, this will be 
-- the global.number[0] variable.

alias example_explicit = global.number[0]
-- This will generate a warning. The compiler will tell you that the other 
-- alias, `example_allocated`, just happened to end up using the variable 
-- that you're now also assigning to this alias.

alias example_borrowed = example_allocated
-- This does not generate a warning. You clearly intend for both of these 
-- aliases to point to the same variable, because you're making the one 
-- alias point to the other alias.

The reason this warning exists is because if you run into this situation, it likely won't be on purpose. While it is possible to predict which variable will be allocated to any given alias, the whole point of allocating variables to aliases (as opposed to choosing variables manually) is that you shouldn't actually have to care what variable ends up in that alias.

If you get this warning, you would generally want to either allocate variables to aliases after all manually-set aliases, or change the manually-set alias to one that allocates a variable.

User-defined functions

Allocation-to-aliases works inside of user-defined functions; however, there are technical limitations that arise as a result of flaws in ReachVariantTool's design.

If a user-defined function is in scope, and the function definition contains aliases, then none of the aliased variables can be allocated to a new alias until the function goes out of scope. This is an exception to the normal rules for allocation-to-aliases, and is a safety measure to prevent variable collisions in situations like the following:

function some_helper_function()
   alias local_variable = allocate temporary number
   
   local_variable = 5
end

alias some_important_variable = allocate temporary number
some_important_variable = 1

some_helper_function()

if some_important_variable != 1 then
   game.show_message_to(all_players, none, "Oh no!")
end

In that code snippet, we allocate a temporary variable to an alias, local_variable, inside the function. However, once we reach the end of the function body, that alias is, technically, out of scope. The only way to prevent some_important_variable from accidentally reusing that same variable — leading to a variable collision, where calling the function accidentally clobbers data in the caller — is to make it so that variables that are aliased in any in-scope function are reserved.

This is a very imperfect workaround. In the following situation, for example, both of the user-defined functions are forced to use separate variables even though there's no risk of a collsiion between them (because neither function calls the other):

function first()
   alias first_alias = allocate temporary number
end

function second()
   alias second_alias = allocate temporary number
end

This is all necessary because of technical limitations in ReachVariantTool's design: it only reads your code once, and it never backtracks to revise what it's compiled. When it sees the definition of second_alias, it hasn't yet read the rest of second(), so it doesn't know for sure whether second() calls first(); it only knows that second() can call first(), so it gives second() a separate variable in order to avoid any potential collisions.

A side-effect of this is that if you define a function outside of any other block, and that function allocates temporary variables, then you will accidentally reserve those variables for the entire rest of the script, preventing any other code from reusing them. One fix for this is to nest your functions in whatever blocks of code actually use them.

Rules for alias names

An alias's name can't begin with a numeric digit, and can't contain square brackets or periods.

Broadly speaking, an alias can't shadow anything built-in:

--
-- These next few rules apply to both relative and absolute aliases, even though 
-- relative aliases wouldn't *always* shadow built-ins in these cases.
--

-- ERROR: An alias's name can never be the name of a keyword.
alias do       = player.number[0] -- relative will fail
alias if       = global.number[0] -- absolute will fail
alias allocate = allocate temporary number -- fails

-- ERROR: An alias's name can never be a namespace name.
alias enums = player.number[0] -- relative will fail
alias game  = global.number[0] -- absolute will fail

-- ERROR: An alias's name can never be the name of a built-in type.
alias player        = global.number[0]
alias timer         = global.number[0]
alias script_traits = global.number[0]
alias script_widget = global.number[0]

--
-- The above rules apply even if the alias is relative. The below rules take into 
-- account whether the alias is relative or absolute.
--

-- ERROR: Shadows non-namespaced built-in values
alias none            = global.number[0] -- a value for several different types
--
alias announce_slayer = global.number[0] -- a  Sound value
alias box             = global.number[0] -- a  Shape value
alias enemies         = global.number[0] -- a  Player Set value
alias warthog         = global.number[0] -- an Object Type value
--
-- Note that `alias none = player.number[0]` would succeed, because `player.none` 
-- doesn't a shadow built-in value name.

-- ERROR: Shadows action `rand`
alias rand = global.number[0]

-- ERROR: Shadows action `object.set_waypoint_visibility`
alias set_waypoint_visibility = object.number[0]

-- ERROR: Shadows condition `object.shape_contains`
alias shape_contains = object.number[0]

-- ERROR: Shadows property `player.biped`
alias biped = player.number[0]

-- ERROR: Shadows access to `player.timer` variables
alias timer = player.number[0]