-
Notifications
You must be signed in to change notification settings - Fork 30
DuckShell
DuckShell is an alternative command engine for the DevConsole. It offers many advanced features, such as running multiple commands consecutively in one command, using a command's output inside another command, native variable support, basic arithmetic and string manipulation/comparison, and built-in command autocompletion.
DuckShell is also mostly compatible with the vanilla command engine, so most automated things that execute console commands still work, with the only exceptions being the newly added syntax and rare exceptional command name changes.
DuckShell supports external scripting as well, in the form of .dsh files. These files can be put in your ~/config/DuckGame/Scripts/ directory, located in %APPDATA% on windows, or ~/.config/ on linux. These files can then be executed on demand with the source <file name> command, and can even be run on startup if you use the -command 'source <file name>' launch argument.
In DuckShell (DSH), most things are a command themselves. Want to add two numbers? use the + command, and thus comes the syntax + 2 2 to add 2 and 2, where + is just the name of the command. The same is for getting and setting variables, with get and set, and various others features.
You can also make any special character be treated as a normal character by using a backslash (\) before it, and even backslashes themselves. This can be useful for if you want to use said special characters even when using them as usual would cause unwanted behaviour.
I'm not gonna explain all the commands here, since you can see every command and it's description with the help command in-game.
Using the syntax [big string] will make it so everything inside the square brackets is treated as a single word.
This can be used when a command accepts multiple arguments, but you want something with whitespace as one argument, such as in the join command, where you may want pure whitespace or text containing whitespace being treated as a single argument.
Big strings are active by default when writing the last argument of a command, such as in the echo command, which has only one argument. So echo 1 2 3 can also be seen as echo [1 2 3], however, join 1 2 3 is not the same as join [1 2] 3, because it expects multiple arguments, and non-last arguments will be taken separately.
Using the syntax {inline command} inside of your command will run what's inside the curly braces and use it's output value in place of the inline command.
For example, the ping command returns "Pong!", which you can use with the join command to have something like join [The robot says: ] {ping}, which prints "The robot says: Pong!".
This is most prominent with getting the value of variables or doing dynamic arithmetic, since those are handled as commands, and need to be executed separately inside of the command before being handled.
Using semicolons (;) will end the current command, and begin executing whatever follows the semicolon as another command.
The semicolons also have to be separated by whitespace from any other text, or it will be treated as part of that text (i.e, it can't be touching anything but air).
This is mostly useful for writing scripts, where you need more complexity in executing multiple commands, as well as readable formatting for your script.
Also importantly, if one command fails in the chain, everything else will end and fail with the same error.
Surrounding text with hashes # will make it a comment, and will be ignored by the DSH interpreter.
This is only really useful for scripts.
To begin with, DSH operates in the format
command arg1 arg2 ..
For example
echo [Hello, World!]
But in a script, you may want more than just one command, in which case
command1 ; command2
Will run command1, and if it didn't fail, proceed to command2
A common usage is for initializing variables
set myName Firebreak ; get myName
Will set the variable myName to "Firebreak", and then print its value with get
I'd also like to note that all whitespace is treated the same, so you can replace spaces with newlines for better formatting here
set myName Firebreak ;
get myName
You can also place these at the end of commands with nothing after, and nothing would happen. So you can just get used to putting them after every command
set myName Firebreak ;
get myName ;
You may also want to use a command's output inside another command, most commonly with arithmetic or variables. For example
set i 0 ;
+ 1 {get i}
This sets a variable i to 0, then prints 1 + i. Because getting the value of variables requires a command, you use inline commands to use it in another command.
In something more complex, such as a loop, you would see this often.
set i 0 ; #initialize i to 0#
loop 5 [ #loop 5 iterations#
get i ; #print the value of i#
set i {+ 1 {get i}} ; #set the value of i to 1 + i#
] ;
which outputs
0
1
2
3
4
And notice how the loop command takes in a Big String to run commands. This is a common pattern in DSH for when you want to dynamically run commands, but not necessarily as part of the command, such as in Inline Commands
Now that we've gone over running complex command chains, let's make our own custom commands
You can make a command with
def <command name> <command args seperated by commas> [<command>]
This will set <command name> as a command that executes <command> when run, and will also override any previous command with that name (even default commands!)
Let's start with a basic command
def welcome name [
join [Hello ] {get name} [!]
]
So then you can run
welcome [John Smith]
Which prints
Hello John Smith!
And another command that takes two arguments
def introduce name,age [
join [Hi! I'm ] {get name} [, and I'm ] {get age} [ years old.]
]
And as expected
introduce Firebreak 18
Hi! I'm Firebreak and I'm 18 years old.
Also notice how I'm using get to retrieve the arguments here, as if they're actually variables. That's because behind the scenes, they are. The def command will create the variables when running the command with the values given to it, and then remove the variables.
Importantly, this means that any variable using the name of one of your arguments will get overwritten and deleted after you run the command. I suggest prefixing your argument names with something unique, like "l_" or just "$" (since DSH is quite lenient with variable names), that way your usual variables don't get overwritten by accident.
Here's the refactor
def introduce $name,$age [
join [Hi! I'm ] {get $name} [, and I'm ] {get $age} [ years old.]
]
I kinda glossed over this for a while, but conditionals are also a thing (as a command, of course)
We'll use them to make a command that checks if a person is of legal age or not in a bit.
First, the conditional command's syntax is as follows
? <condition> <commandIfTrue>
Which will run <commandIfTrue> if <condition> is true. You can think of "?" as "if"
For example
? {= 1 1} [echo 1 equals 1.]
Outputs
1 equals 1.
However in this example, if the condition is false, nothing happens
? {= 1 2} [echo 1 equals 2.]
Outputs
To handle exceptional cases like these, you need the full syntax of the conditional command
-
?- if -
:- else -
:?- else if
Now we can handle the false case
? {= 1 2} [
echo 1 equals 2.
] : [
echo 1 does not equal 2.
]
And I'm just formatting this differently for readability, but it's the same deal.
Now, this outputs
1 does not equal 2.
So we've successfully handled the alternative case.
Now for the islegal command:
def islegal $age [
? {< {get $age} 18} [
echo You are underaged
] : [
echo You are of legal age
]
]
For something more complex, you'd use :? and provide a second condition and command, and keep going like that.
For example, let's make an agerange command in the same spirit
def agerange $age [
? {< {get $age} 13} [
echo Child
] :? {< {get $age} 20} [
echo Teenager
] : [
echo Adult
]
]
And using it
agerange 36
Adult
agerange 19
Teenager
agerange 13
Teenager
agerange 6
Child