Here I will be sharing some interesting things I've found about Xonotic console.
Basics
You can insert contents of any cvar anywhere: echo "ctf: ${g_ctf}" → "ctf: 1".
If the cvar doesn't exist, the character combination will be left unchanged and a warning will be shown. To avoid this: echo "test${unexisting_cvar ?}test" → "testtest".
It is possible to 'go deeper' and get a variable value by its name stored in another variable: set myvar 5; set myvarname "myvar"; ${$myvarname} → 5
Similar argument expansion exists for the alias command.
${1} means the first parameter passed to a command created with alias.
${3-} means all the parameters starting with the third one.
${*} means all the parameters, but I prefer ${1-}.
${4 ?} means the fourth parameter, or nothing if it wasn't passed.
${2 asis} means the second parameter, without escaping quotes (because by default they are escaped)
${* q}, ${1- q} means all the parameters as they were provided, escaped (spaces and quotes work)
alias test "echo ${3},${2-},${5 ?},${1 ?}"; test v1 v2 v3 v4 → "v3,v2 v3 v4,,v1"
These parentheses can be omitted ($g_dm or $1-), but not in all the cases. I like to always put them: it makes variable substitutions more noticeable.
It is possible to have 'nested' quotes by escaping them: alias go "say Wait for it...; defer 3 \"say GO!\""
If you bind a command with a plus in front of it to a key, the same command, only with minus, will be called when the key is released: +command, -command.
toggle is quite an interesting command. It switches a variable between 0 and 1 each call, or between the provided arguments: toggle some_var val1 val2 val3 val4.
defer executes the command you give to it after some seconds: defer 4.7 disconnect. And defer clear removes all pending actions.
???
PROFIT!
Advanced Console Usage
Conditionals
Let's start with making an if command. Yes, that's right, we will be able to execute commands depending on cvar values.
It will test if the value passed is a nonzero number and execute the following command only if it is so.
It can be used like this:
bx_if 5 echo "Yup" → "Yup"
bx_if 0 echo "Nope" → ×
bx_if ${g_ctf} echo "You are playing a CTF game now."
set bx_if_var_ "" // create a variable that will hold the value which is being tested
alias bx_if "bx_if_var_ ${1};············· // set our variable to the supplied value
·············toggle bx_if_var_;··········· // toggle it, so that all the nonzero values become zero
·············alias bx_if_action0_ ${2-};·· // make an action that will be executed if the value is zero (i. e. nonzero was supplied)
·············alias bx_if_action1_ bx_noop; // if the value is zero, nothing will be done (alias bx_noop "" was defined earlier)
·············bx_if_do_"··················· // call a "proxy" command, because cvars are expanded at the very beginning!
alias bx_if_do_ "bx_if_action${bx_if_var_}_" // bx_if_action0_ or bx_if_action1_ will be called depending on the variable
You may have noticed a naming convention I've used. An underscore in the end means that the command or variable is for internal use. And no, these newlines in between of a command are not allowed. This is just for presentation.
Key Combinations
set bx_shift_state 0
alias +bx_shift "+crouch; bx_shift_state 1"
alias -bx_shift "-crouch; bx_shift_state 0"
bind SHIFT "+bx_shift"
alias bx_announce_dropped "say_team Dropped %w.; g_waypointsprite_team_here"
bind G "bx_if ${bx_shift_state} bx_announce_dropped; wait; dropweapon"
If you press the G key, you will drop the current weapon. But an announcement about it to your team will be made only if Shift is down.
RPN
Before reading make sure you know what is Reverse Polish Notation.
Xonotic has a command named rpn. It allows to do various operations on a stack, such as math, but not only that.
A quick example: rpn 1 2 + 4 * → "rpn: still on stack: 12".
rpn differentiates between 3 data types: float (numeric), string (text, which should be prepended with '/') and set (string of space-separated console tokens). To push these values onto the stack you just write them. If you write text without a '/' symbol before it, rpn interprets it as a variable name and replaces the name with the value of that variable.
set testvar 47; rpn 6.28 /sometext "1 2 3" testvar → "rpn: still on stack: 47 \ rpn: still on stack: 1 2 3 \ rpn: still on stack: sometext \ rpn: still on stack: 6.28"
Now, for a reference of what rpn can do.
'x' will stand for any data type (plain text), 'n' for numbers, 's' for sets.
[x, pop]··→··[] // remove the top item.
[x, dup]··→··[x, x] // duplicate the top item.
[x1, x2, exch]··→··[x2, x1] // exchange the two top items.
[/cvar, load]··→··[x] // load a variable by name cvar onto the stack
[/cvar, x, (def|=)]··→··[] // save a value from the stack into the variable with name cvar
[n1, n2, (add|+ | sub|- | mul|* | div|/ | mod|% | max | min)]··→··[n] // binary operations
[n1, n2, (eq|== | ne|!= | gt|> | ge|>= | lt|< | le|<=)]··→··[n] // binary comparisons: 1 if true, 0 if false
[n, (neg, abs, sgn, floor, ceil)]··→··[n] // unary operations
[n1, n2, n3, bound]··→··[n] // limits n2 between n1 and n3
[n1, n2, n3, when]··→··[n] // if n3 is nonzero, the result is n1, else n2
[n, rand]··→··[n] // random integer between 0 and n-1
[time]··→··[n] // number of seconds (with fractional part) the game has been running for
[x, (/MD4 | /SHA256), digest]··→··[x] // hashing
[s, s, (union | intersection | difference)]··→··[s] // set operations
[s, shuffle]··→··[s] // shuffle elements of a sequence
There is also other functionality, even databases (which I don't quite understand). You can find more information in the "usage" text of rpn which is at the bottom of this file.
Some examples:
rpn 5 6 lt → "rpn: still on stack: 1"
rpn 5 6 >= → "rpn: still on stack: 0"
alias bx_inc "rpn /${1} dup load 1 + =" // increase a cvar by 1: $1 = $1+1; load is used to avoid clashes with rpn commands (e.g. if $1 is intersection)
alias bx_random_range "rpn /bx_random_range_r ${2} ${1} - 1 + rand ${1} + =" // a random number between $1 and $2 inclusively: bx_random_range_r = rand($2-$1+1)+$1
rpn "/1 2 3" "/2 3 4" intersection → "rpn: still on stack: 2 3".
rpn /test_string /MD4 digest → "rpn: still on stack: 11e0c1ec8d9fbc76aa8fff26e44ce627"
Hash-Based String Comparison
alias bx_if_eq "rpn /bx_if_eq_var1_ \"/${1}\" /SHA256 digest =; // save the hash of the first var
················rpn /bx_if_eq_var2_ \"/${2}\" /SHA256 digest =; // save the hash of the second var
················alias bx_if_eq_action_ ${3- q};··························// save the action
················bx_if_eq_step2_"
alias bx_if_eq_step2_ "set bx_if_eq_resultvar_ \"bx_if_eq_var_${bx_if_eq_var1_}_\"; // save the name of the result var
·······················set bx_if_eq_var_${bx_if_eq_var1_}_ 0; // set the variable with the 1st hash in its name to 0
·······················set bx_if_eq_var_${bx_if_eq_var2_}_ 1; // if hashes are the same, this will change the same variable, so the result var will now hold 1
·······················bx_if_eq_step3_"
alias bx_if_eq_step3_ "bx_if ${$bx_if_eq_resultvar_} bx_if_eq_action_" // execute the action only if result is 1: hashes are equal
Hash collisions (if you know what I'm talking about) are possible in theory, but not in practice.
Miscellaneous
Weapons
Weapon Hook
When you change your active weapon, the game calls the alias cl_hook_activeweapon with a parameter that is the name of the selected weapon. Here's what I do with it:
set bx_weapon "shotgun" // default value
alias cl_hook_activeweapon "bx_weapon ${1}; bx_on_weapon_change" // now we have the current weapon name in a variable, and can execute an action, too!
alias bx_on_weapon_change "hud_panel_physics 3; bx_if_eq ${bx_weapon} laser hud_panel_physics 2" // show speed HUD panel only when laser is selected
I will show one more way to use this variable in the next section.
Weapon Switching
Don't you think that the current way to switch weapons – one, sometimes two per key – is somewhat inefficient? Well, there's a better way. Go to the console and type search cl_weapon* right now.
So, the cl_weaponpriorityN cvars can hold individual weapon cycling lists, to be used by impulse 20N, impulse 21N and impulse 22N. I'll just leave my binds here.
cl_weaponpriority1 "crylink shotgun"; cl_weaponpriority2 "hagar uzi hlac"; cl_weaponpriority3 "rocketlauncher electro grenadelauncher"; cl_weaponpriority4 "minstanex nex rifle"; cl_weaponpriority5 "fireball seeker minelayer porto"; cl_weaponpriority0 "laser hook"
bind 1 "impulse 221"; bind 2 "impulse 222"; bind 3 "impulse 223"; bind 4 "impulse 224"; bind 5 "impulse 225"; bind 6 "impulse 226"
bind Q "bx_if_not_eq ${bx_weapon} laser impulse 220; bx_if_eq ${bx_weapon} laser weaplast" // if not laser, switch to laser, else switch to previous weapon
Weapon Combos
Yes, weaplast command (Q button by default) is useful, but imagine this situation: you have a 2-weapon combo, you keep switching between those weapons, shoot your opponent... but then you need to use the laser, so your 'combo' is broken: now it is one of the weapons and laser. Well, I made a similar bx_invprev command that does just the same, but excludes laser! You can use the laser and then the command can switch back to your combo of 2 weapons.
alias bx_on_weapon_change "bx_save_last_weapon_ ${bx_weapon}" // save new weapon selection
set bx_last_weapons "" //this variable will hold 2 last weapons, separated with space
alias bx_save_last_weapon_ "rpn /bx_last_weapons_temp_ /${1} bx_last_weapons union /shotgun union \"/laser none\" difference =; // save ($1∪$bx_last_weapons∪shotgun)–(laser∪none)
····························bx_save_last_weapon_step2_"
alias bx_save_last_weapon_step2_ "bx_save_last_weapon_step3_ ${bx_last_weapons_temp_}" // a relay so we can cut off all arguments except the first 2
alias bx_save_last_weapon_step3_ "bx_last_weapons \"${1 ?} ${2 ?}\""·················· // save the first 2 weapons from the 'set'
alias bx_invprev "bx_switch_weapon2_ ${bx_last_weapons} shotgun shotgun" // shotgun is added in case the variable is empty
alias bx_switch_weapon2_ "weapon_${2}" // many arguments are passed, but we take only the 2nd
bind E "bx_invprev" // it is probably more usual to put Q here, but I use it for laser
Game Mode Hooks
When a round starts, the game calls cl_hook_gamestart_all. When it ends, the game calls cl_hook_gameend.
The game also calls cl_hook_gamestart_MODE at the beginning of every round, depending on the game mode: arena, as, ca, ctf, cts, dm, dom, ft, ka, kh, lms, nb, nop, ons, rc, rune or tdm.
Have fun using what you've learned. Make sure to tell me your interesting ideas about all of this.
And please, mention me if you want to 'retell' some of this information.
Thanks to Mr. Bougo for explaining or pointing to many of the game's secrets.
Change Log
Basics
You can insert contents of any cvar anywhere: echo "ctf: ${g_ctf}" → "ctf: 1".
If the cvar doesn't exist, the character combination will be left unchanged and a warning will be shown. To avoid this: echo "test${unexisting_cvar ?}test" → "testtest".
It is possible to 'go deeper' and get a variable value by its name stored in another variable: set myvar 5; set myvarname "myvar"; ${$myvarname} → 5
Similar argument expansion exists for the alias command.
${1} means the first parameter passed to a command created with alias.
${3-} means all the parameters starting with the third one.
${*} means all the parameters, but I prefer ${1-}.
${4 ?} means the fourth parameter, or nothing if it wasn't passed.
${2 asis} means the second parameter, without escaping quotes (because by default they are escaped)
${* q}, ${1- q} means all the parameters as they were provided, escaped (spaces and quotes work)
alias test "echo ${3},${2-},${5 ?},${1 ?}"; test v1 v2 v3 v4 → "v3,v2 v3 v4,,v1"
These parentheses can be omitted ($g_dm or $1-), but not in all the cases. I like to always put them: it makes variable substitutions more noticeable.
It is possible to have 'nested' quotes by escaping them: alias go "say Wait for it...; defer 3 \"say GO!\""
If you bind a command with a plus in front of it to a key, the same command, only with minus, will be called when the key is released: +command, -command.
toggle is quite an interesting command. It switches a variable between 0 and 1 each call, or between the provided arguments: toggle some_var val1 val2 val3 val4.
defer executes the command you give to it after some seconds: defer 4.7 disconnect. And defer clear removes all pending actions.
???
PROFIT!
Advanced Console Usage
Conditionals
Let's start with making an if command. Yes, that's right, we will be able to execute commands depending on cvar values.
It will test if the value passed is a nonzero number and execute the following command only if it is so.
It can be used like this:
bx_if 5 echo "Yup" → "Yup"
bx_if 0 echo "Nope" → ×
bx_if ${g_ctf} echo "You are playing a CTF game now."
set bx_if_var_ "" // create a variable that will hold the value which is being tested
alias bx_if "bx_if_var_ ${1};············· // set our variable to the supplied value
·············toggle bx_if_var_;··········· // toggle it, so that all the nonzero values become zero
·············alias bx_if_action0_ ${2-};·· // make an action that will be executed if the value is zero (i. e. nonzero was supplied)
·············alias bx_if_action1_ bx_noop; // if the value is zero, nothing will be done (alias bx_noop "" was defined earlier)
·············bx_if_do_"··················· // call a "proxy" command, because cvars are expanded at the very beginning!
alias bx_if_do_ "bx_if_action${bx_if_var_}_" // bx_if_action0_ or bx_if_action1_ will be called depending on the variable
You may have noticed a naming convention I've used. An underscore in the end means that the command or variable is for internal use. And no, these newlines in between of a command are not allowed. This is just for presentation.
Key Combinations
set bx_shift_state 0
alias +bx_shift "+crouch; bx_shift_state 1"
alias -bx_shift "-crouch; bx_shift_state 0"
bind SHIFT "+bx_shift"
alias bx_announce_dropped "say_team Dropped %w.; g_waypointsprite_team_here"
bind G "bx_if ${bx_shift_state} bx_announce_dropped; wait; dropweapon"
If you press the G key, you will drop the current weapon. But an announcement about it to your team will be made only if Shift is down.
RPN
Before reading make sure you know what is Reverse Polish Notation.
Xonotic has a command named rpn. It allows to do various operations on a stack, such as math, but not only that.
A quick example: rpn 1 2 + 4 * → "rpn: still on stack: 12".
rpn differentiates between 3 data types: float (numeric), string (text, which should be prepended with '/') and set (string of space-separated console tokens). To push these values onto the stack you just write them. If you write text without a '/' symbol before it, rpn interprets it as a variable name and replaces the name with the value of that variable.
set testvar 47; rpn 6.28 /sometext "1 2 3" testvar → "rpn: still on stack: 47 \ rpn: still on stack: 1 2 3 \ rpn: still on stack: sometext \ rpn: still on stack: 6.28"
Now, for a reference of what rpn can do.
'x' will stand for any data type (plain text), 'n' for numbers, 's' for sets.
[x, pop]··→··[] // remove the top item.
[x, dup]··→··[x, x] // duplicate the top item.
[x1, x2, exch]··→··[x2, x1] // exchange the two top items.
[/cvar, load]··→··[x] // load a variable by name cvar onto the stack
[/cvar, x, (def|=)]··→··[] // save a value from the stack into the variable with name cvar
[n1, n2, (add|+ | sub|- | mul|* | div|/ | mod|% | max | min)]··→··[n] // binary operations
[n1, n2, (eq|== | ne|!= | gt|> | ge|>= | lt|< | le|<=)]··→··[n] // binary comparisons: 1 if true, 0 if false
[n, (neg, abs, sgn, floor, ceil)]··→··[n] // unary operations
[n1, n2, n3, bound]··→··[n] // limits n2 between n1 and n3
[n1, n2, n3, when]··→··[n] // if n3 is nonzero, the result is n1, else n2
[n, rand]··→··[n] // random integer between 0 and n-1
[time]··→··[n] // number of seconds (with fractional part) the game has been running for
[x, (/MD4 | /SHA256), digest]··→··[x] // hashing
[s, s, (union | intersection | difference)]··→··[s] // set operations
[s, shuffle]··→··[s] // shuffle elements of a sequence
There is also other functionality, even databases (which I don't quite understand). You can find more information in the "usage" text of rpn which is at the bottom of this file.
Some examples:
rpn 5 6 lt → "rpn: still on stack: 1"
rpn 5 6 >= → "rpn: still on stack: 0"
alias bx_inc "rpn /${1} dup load 1 + =" // increase a cvar by 1: $1 = $1+1; load is used to avoid clashes with rpn commands (e.g. if $1 is intersection)
alias bx_random_range "rpn /bx_random_range_r ${2} ${1} - 1 + rand ${1} + =" // a random number between $1 and $2 inclusively: bx_random_range_r = rand($2-$1+1)+$1
rpn "/1 2 3" "/2 3 4" intersection → "rpn: still on stack: 2 3".
rpn /test_string /MD4 digest → "rpn: still on stack: 11e0c1ec8d9fbc76aa8fff26e44ce627"
Hash-Based String Comparison
alias bx_if_eq "rpn /bx_if_eq_var1_ \"/${1}\" /SHA256 digest =; // save the hash of the first var
················rpn /bx_if_eq_var2_ \"/${2}\" /SHA256 digest =; // save the hash of the second var
················alias bx_if_eq_action_ ${3- q};··························// save the action
················bx_if_eq_step2_"
alias bx_if_eq_step2_ "set bx_if_eq_resultvar_ \"bx_if_eq_var_${bx_if_eq_var1_}_\"; // save the name of the result var
·······················set bx_if_eq_var_${bx_if_eq_var1_}_ 0; // set the variable with the 1st hash in its name to 0
·······················set bx_if_eq_var_${bx_if_eq_var2_}_ 1; // if hashes are the same, this will change the same variable, so the result var will now hold 1
·······················bx_if_eq_step3_"
alias bx_if_eq_step3_ "bx_if ${$bx_if_eq_resultvar_} bx_if_eq_action_" // execute the action only if result is 1: hashes are equal
Hash collisions (if you know what I'm talking about) are possible in theory, but not in practice.
Miscellaneous
Weapons
Weapon Hook
When you change your active weapon, the game calls the alias cl_hook_activeweapon with a parameter that is the name of the selected weapon. Here's what I do with it:
set bx_weapon "shotgun" // default value
alias cl_hook_activeweapon "bx_weapon ${1}; bx_on_weapon_change" // now we have the current weapon name in a variable, and can execute an action, too!
alias bx_on_weapon_change "hud_panel_physics 3; bx_if_eq ${bx_weapon} laser hud_panel_physics 2" // show speed HUD panel only when laser is selected
I will show one more way to use this variable in the next section.
Weapon Switching
Don't you think that the current way to switch weapons – one, sometimes two per key – is somewhat inefficient? Well, there's a better way. Go to the console and type search cl_weapon* right now.
So, the cl_weaponpriorityN cvars can hold individual weapon cycling lists, to be used by impulse 20N, impulse 21N and impulse 22N. I'll just leave my binds here.
cl_weaponpriority1 "crylink shotgun"; cl_weaponpriority2 "hagar uzi hlac"; cl_weaponpriority3 "rocketlauncher electro grenadelauncher"; cl_weaponpriority4 "minstanex nex rifle"; cl_weaponpriority5 "fireball seeker minelayer porto"; cl_weaponpriority0 "laser hook"
bind 1 "impulse 221"; bind 2 "impulse 222"; bind 3 "impulse 223"; bind 4 "impulse 224"; bind 5 "impulse 225"; bind 6 "impulse 226"
bind Q "bx_if_not_eq ${bx_weapon} laser impulse 220; bx_if_eq ${bx_weapon} laser weaplast" // if not laser, switch to laser, else switch to previous weapon
Weapon Combos
Yes, weaplast command (Q button by default) is useful, but imagine this situation: you have a 2-weapon combo, you keep switching between those weapons, shoot your opponent... but then you need to use the laser, so your 'combo' is broken: now it is one of the weapons and laser. Well, I made a similar bx_invprev command that does just the same, but excludes laser! You can use the laser and then the command can switch back to your combo of 2 weapons.
alias bx_on_weapon_change "bx_save_last_weapon_ ${bx_weapon}" // save new weapon selection
set bx_last_weapons "" //this variable will hold 2 last weapons, separated with space
alias bx_save_last_weapon_ "rpn /bx_last_weapons_temp_ /${1} bx_last_weapons union /shotgun union \"/laser none\" difference =; // save ($1∪$bx_last_weapons∪shotgun)–(laser∪none)
····························bx_save_last_weapon_step2_"
alias bx_save_last_weapon_step2_ "bx_save_last_weapon_step3_ ${bx_last_weapons_temp_}" // a relay so we can cut off all arguments except the first 2
alias bx_save_last_weapon_step3_ "bx_last_weapons \"${1 ?} ${2 ?}\""·················· // save the first 2 weapons from the 'set'
alias bx_invprev "bx_switch_weapon2_ ${bx_last_weapons} shotgun shotgun" // shotgun is added in case the variable is empty
alias bx_switch_weapon2_ "weapon_${2}" // many arguments are passed, but we take only the 2nd
bind E "bx_invprev" // it is probably more usual to put Q here, but I use it for laser
Game Mode Hooks
When a round starts, the game calls cl_hook_gamestart_all. When it ends, the game calls cl_hook_gameend.
The game also calls cl_hook_gamestart_MODE at the beginning of every round, depending on the game mode: arena, as, ca, ctf, cts, dm, dom, ft, ka, kh, lms, nb, nop, ons, rc, rune or tdm.
Have fun using what you've learned. Make sure to tell me your interesting ideas about all of this.
And please, mention me if you want to 'retell' some of this information.
Thanks to Mr. Bougo for explaining or pointing to many of the game's secrets.
Change Log
- 5 May 2012 - initial version
- 11 May 2012 - added RPN, var value by name, nested quotes, string comparison, 'q' and 'asis' substitution, weapon hook, gamemode hooks; minor improvements
- 12 May 2012 - fixed string comparison, intersection example, inc example
- 10 Jun 2012 - added previous weapon without laser, syntax highlight; minor fixes and improvements