Player guide
Console scripting
Four additions turn binds and configs from fixed strings into something you can branch on, substitute into, and schedule. They are most useful together, so they are documented together.
Everything here came from NewJK and NewMod, and
their documentation is the
original account of ifCvar and strSub. This page describes what the TaystJK client
actually does, which is not identical. See what does not work.
The console reference has the catalogue entries:
ifCvar,
strSub,
delay,
waitf,
delaycancel and
waitfcancel.
Quoting and nested quotes
Stock Jedi Academy ends a quoted string at the next ", so a bind cannot contain a bind.
TaystJK tracks quote depth instead, and the rule that makes it work is worth stating
directly:
Each time a command is executed, exactly one layer of quotes is removed.
So this is a single bind that, when pressed, creates another bind:
bind a "bind b "set c "d ; e" ; say "hello there"""
Pressing A peels the outer quotes and runs bind b "set c "d ; e" ; say "hello
there"". Pressing B peels the next layer. Count your closing quotes at the end;
three in a row is not a typo.
A " counts as opening a level only when the character after it is not a space, a ;
or another ", and the character before it is not a "
(IsOpeningQuote).
That heuristic is what lets the parser tell say "hi" from a stray quote, and it is why
"" is treated as an empty argument rather than as one level of nesting.
Three places count depth the same way, which is why the feature holds up across all the
routes a command can take: the command buffer, so a ; inside quotes no longer splits the
line
(Cbuf_Execute);
the tokenizer that splits a line into arguments
(Cmd_TokenizeStringNestedQuotes,
used by
Cmd_ExecuteString);
and bind parsing, so a bound string containing ; inside quotes stays one command
(CL_ParseBinding).
There is no backslash escape. \" is not special; depth counting is the only mechanism.
Substituting cvar values: strSub
strSub runs the command that follows it, first replacing every $cvarname$ with that
cvar’s current value
(Com_StrSub_f).
strSub say "Hello, I am $name$"
The name is delimited on both sides by $. Write $$ for a literal dollar sign
(common.cpp:454).
Substitution happens per argument, so a value containing spaces is re-quoted as one
argument before the command runs.
This is the difference between say My fps cap is $com_maxfps$ under strSub, which sends
the number, and vstr, which executes a cvar’s contents as a command. Use strSub when you
want a value inside a string; use vstr when the cvar is the command.
Branching on a cvar: ifCvar
ifCvar reads one cvar, tests it against conditions in order, and runs the command
belonging to the first match
(Com_IfCvar_f).
Nothing runs if no condition matches.
ifCvar <cvar> <setting> <count> <command...> [<setting> <count> <command...> ...]
The simplest form compares the value as text:
ifCvar cg_myCvar 0 2 say_team hi 1 2 say_team bye
If cg_myCvar is 0 it says hi; if it is 1 it says bye. The 2 before each command
is the argument count, explained below.
Modifiers
A setting may start with one of these. Without one, the comparison is a case-insensitive string match.
| Modifier | Test |
|---|---|
$= |
Numeric equality |
$!= |
Numeric inequality |
$> |
Numerically greater than |
$< |
Numerically less than |
$>= |
Numerically greater than or equal |
$<= |
Numerically less than or equal |
$contains |
The value contains this text anywhere |
$beginswith |
The value starts with this text |
$startswith |
Alias for $beginswith |
$endswith |
The value ends with this text |
$else |
Always true |
$startswith is accepted alongside $beginswith
(common.cpp:394);
the NewMod documentation lists only $beginswith.
The text follows the modifier with no space: $containsbeer, $>=50, $else. Put a
$ in front of the comparison value to read it from another cvar instead of using it
literally, for example $>=$cg_someOtherCvar. That indirection works for the six numeric operators
and for plain equality; it does not work for $contains, $beginswith, $startswith
or $endswith, for the reasons in what does not work.
$else should be last. It always matches, so anything after it is unreachable.
Counting arguments
The number before each command is how many arguments that command occupies, including the command’s own name. This is the part that trips people up.
| Command to run | Count | Why |
|---|---|---|
quit |
1 |
The command name only. |
say_team hi |
2 |
Name plus one word. |
set model desann |
3 |
Name plus two words. |
bind x say_team "hello there" |
4 |
"hello there" is quoted, so it is one argument. |
A quoted phrase counts once, and the client re-adds the quotes when it runs the command
(common.cpp:417).
Get the count wrong and ifCvar reads the following condition from the middle of your
command; the count must be between 1 and 1023 or it refuses and prints why
(common.cpp:410).
Putting it together:
ifCvar cg_myCvar $>=$cg_someOtherCvar 1 quit $containsbeer 3 set model desann $else 4 bind x say_team "hello there"
Greater than or equal to cg_someOtherCvar quits; otherwise a value containing beer
switches model; otherwise the bind is set.
Timed commands: delay and waitf
Stock wait stalls the entire command buffer. delay and waitf do not: they set the rest
of the line aside and let everything else keep running. delay counts milliseconds,
waitf counts frames.
say darth;delay 1000;say vader
Two things about the syntax are not obvious, and both follow from these being intercepted
in the command buffer rather than run as ordinary commands
(Cbuf_Execute):
- A
;or a newline must follow the number.delay 1000 say vaderprints usage and does nothing.delay 1000;say vaderworks. - Everything after that
;is deferred as one unit, to the end of the line (cmd.cpp:324). Soa;delay 500;b;delay 500;crunsa, waits, then runsb;delay 500;c. The delays chain instead of both counting from now.
Omitting the number entirely is the same as 1, but a space must remain between the
command and the separator: delay ;say vader works, while delay;say vader does not
(cmd.cpp:314).
delaycancel and waitfcancel drop pending entries whose text contains the argument, so
delaycancel vader cancels the example above. delaycancel "" cancels every pending
delay. Each cancels only its own kind.
What does not work
Verified against the source this reference is generated from. These are defects in the client, not deliberate limits, so they may be fixed in a build newer than this page. Compare the dates.
Cvar indirection is broken for the three text operators. $contains$myCvar,
$beginswith$myCvar, $startswith$myCvar and $endswith$myCvar all read the wrong
position when resolving the cvar name
($contains,
$beginswith,
$endswith).
The lookup returns an empty string, and an empty string matches anything, so the condition
silently becomes always true rather than failing visibly. Compare against literal text
with these operators.
$endswith needs at least three characters. $endswithab falls through to plain string
equality instead of testing the suffix
(common.cpp:398).
Do not pass an empty setting. ifCvar someCvar "" 2 say hi reaches a loop that does not
advance
(common.cpp:363).
Use $else when you want a condition that always matches.
strSub needs the closing $. $name without a trailing $ is read to the end of the
argument
(common.cpp:461),
which is rarely the cvar you meant.
Report anything else you hit at https://github.com/taysta/TaystJK/issues.
Last changed History Edit this page on GitHub