Skip to content

Commands (and how to use them!)

What makes both commands (and CVars) special in PikeConsole is that you actually do not initialize them yourself.

Instead we make use of a special Node called the CommandSet
By inheriting from this Node, we can create a command in just a few seconds.

Let's try it out by creating a simple echo command...

1️⃣ Create a Node to host the command set

Right click your scene tree and press Add a child Node or press Ctrl + A.
Create a Node using the root type Node.

Name this Node anything you want. It is recommended to keep the names clear and suffix it with "CommandSet" for hierarchical clarity.

2️⃣ Inherit the CommandSet class on the Node.

Add a script to the Node and have it inherit from the CommandSet class.

Note

The CommandSet should auto-complete in your IDE and automatically import the namespace.
If it doesn't, you need to manually import the the namespace at the top of the file:

1
using FractalPike.PikeConsole.Core.RuntimeExecution.Commands;

Once that is done, you will get prompted to implement the abstract classes.
For VSCode, press Ctrl + . and choose Import abstract class.

You should now have something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
using FractalPike.PikeConsole.Core.RuntimeExecution.Commands;
using System;

public partial class MyCommandSet : CommandSet
{
    protected override Command[] InstantiateCommands()
    {
        throw new NotImplementedException();
    }
}

3️⃣ Add the command to the Node

The InstantiateCommands is a declarative method that is automatically run by the CommandSet Node to initialize commands. Any commands returned by this method will be automatically added to the command registry.

To instantiate the command, we will use the Command() shorthand method that is provided by the CommandSet. This shorthand automatically tags our commands with self-diagnostic metadata.

Tip

For commands that shouldn't be included in release builds of the game you can instead use the shorthand CommandHidden().

Warning

  • Do not instantiate commands using the new keyword.
  • Do not instantiate commands outside the InstantiateCommands() method.

I have written the code for the echo command below...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
using FractalPike.PikeConsole.Core.RuntimeExecution;
using FractalPike.PikeConsole.Core.RuntimeExecution.Commands;
using Godot;

public partial class MyCommandSet : CommandSet
{
    protected override Command[] InstantiateCommands() => [
        Command(
            "my_echo", // Signature
            false, // Is Cheat
            (args) => 
                new(ExecutionResponseStatus.Success, $"{args.Join(" ")}")
        ),
    ];
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using FractalPike.PikeConsole.Core.RuntimeExecution;
using FractalPike.PikeConsole.Core.RuntimeExecution.Commands;
using Godot;

public partial class MyCommandSet : CommandSet
{
    protected override Command[] InstantiateCommands()
    {
        return [
            Command(
                "my_echo", // Signature 
                false, // Is Cheat
                EchoCommand
            ),
        ];
    }

    Response<ExecutionResponseStatus> EchoCommand(string[] args)
    {
        return new(ExecutionResponseStatus.Success, $"{args.Join(" ")}");
    }
}

There is no real performative difference between the two examples. Choose the syntax you like.

Make sure to avoid naming collisions

The echo command is already occupied by the GlobalCommandSet.
Make sure to name your command something else, like my_echo!

4️⃣ Run the command!

That's it!
Now you just need to open up the developer console and type in your new command!

> my_echo Hello world!
Hello world!

Cheat protection is managed automatically. If we temporarily change isCheat parameter to true:

> my_echo Hello world!
my_echo is cheat protected!
> cheatmode 1
Set cheatmode to True
> my_echo Hello world!
Hello world!

🧩 The Command shorthand properties

In the above example we used a custom shorthand provided by the CommandSet API.
This shorthand comes in 2 flavors of parameters: Documented and Quick. Out of the two, the framework will nudge you to use the documented version.

Note

You can turn off warnings for undocumented commands by enabling:
Project settings [General] > Fractal Pike > Pike Console > Supress Documentation Warnings

The API-Reference contains more information about the Command shorthands.

📄 Documenting the echo command

By applying the information above, we can now document our echo command, like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
using FractalPike.PikeConsole.Core.RuntimeExecution;
using FractalPike.PikeConsole.Core.RuntimeExecution.Commands;
using Godot;

public partial class MyCommandSet : CommandSet
{
    protected override Command[] InstantiateCommands() => [
        Command(
                "my_echo",
                "Joins and echoes the arguments back to the caller",
                null,
                "my_echo [args...]",
                false,
                (args) =>
                    new(ExecutionResponseStatus.Success, $"{args.Join(" ")}")
            ),
        ];
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using FractalPike.PikeConsole.Core.RuntimeExecution;
using FractalPike.PikeConsole.Core.RuntimeExecution.Commands;
using Godot;

public partial class MyCommandSet : CommandSet
{
    protected override Command[] InstantiateCommands()
    {
        return [
            Command(
                    "my_echo",
                    "Joins and echoes the arguments back to the caller",
                    null,
                    "my_echo [args...]",
                    false,
                    EchoCommand
                ),
        ];
    }

    Response<ExecutionResponseStatus> EchoCommand(string[] args)
    {
        return new(ExecutionResponseStatus.Success, $"{args.Join(" ")}");
    }
}

Adding documentation to your commands may feel tedious, but it helps immensely with QA testing, runtime experience and remembering what stuff does 6 months from now.

ℹ️ Godot Lifecycle methods inside a CommandSet Node

The CommandSet Node uses _EnterTree, _Ready and _ExitTree for internal functioning, thus if you need to access the lifecycle methods within a CommandSet you override the API-provided wrapper methods instead:

  • OnEnterTree
  • OnReady
  • OnExitTree

The wrapper also contains a method for when cheatmode is edited.
This can be used to force-disable stuff like noclip.

  • OnCheatModeChanged

Here is that code added to our previous example...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using FractalPike.PikeConsole.Core.Logging;
using FractalPike.PikeConsole.Core.RuntimeExecution;
using FractalPike.PikeConsole.Core.RuntimeExecution.Commands;
using Godot;

public partial class MyCommandSet : CommandSet
{
    protected override void OnEnterTree() =>
        PikeLogger.Log(LogTarget.All, $"Tree entered!");

    protected override void OnReady() =>
        PikeLogger.Log(LogTarget.All, $"Node ready!");

    protected override void OnExitTree() =>
        PikeLogger.Log(LogTarget.All, $"Tree exited!");

    protected override void OnCheatModeChanged(bool newState)
    {
        if (newState == false)
        {
            PikeLogger.Log(LogTarget.All, $"Force removing noclip...");
        }
    }

    protected override Command[] InstantiateCommands() => [
        Command(
                "my_echo",
                "Joins and echoes the arguments back to the caller",
                null,
                "my_echo [args...]",
                false,
                (args) =>
                    new(ExecutionResponseStatus.Success, $"{args.Join(" ")}")
            ),
        ];
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using FractalPike.PikeConsole.Core.Logging;
using FractalPike.PikeConsole.Core.RuntimeExecution;
using FractalPike.PikeConsole.Core.RuntimeExecution.Commands;
using Godot;

public partial class MyCommandSet : CommandSet
{
    protected override void OnEnterTree()
    {
        PikeLogger.Log(LogTarget.All, $"Tree entered!");
    }

    protected override void OnReady()
    {
        PikeLogger.Log(LogTarget.All, $"Node ready!");
    }

    protected override void OnExitTree()
    {
        PikeLogger.Log(LogTarget.All, $"Tree exited!");
    }

    protected override void OnCheatModeChanged(bool newState)
    {
        if (newState == false)
        {
            PikeLogger.Log(LogTarget.All, $"Force removing noclip...");
        }
    }

    protected override Command[] InstantiateCommands()
    {
        return [
            Command(
                    "my_echo",
                    "Joins and echoes the arguments back to the caller",
                    null,
                    "my_echo [args...]",
                    false,
                    EchoCommand
                ),
            ];
    }

    Response<ExecutionResponseStatus> EchoCommand(string[] args)
    {
        return new(ExecutionResponseStatus.Success, $"{args.Join(" ")}");
    }
}

📦 Execution responses

All commands must return a response after execution.
The command response builds on the generic Resonse<T> struct containing a Status and a Message.

The Statement Executor will log any response where the Message is not empty.
Commands denied for being cheat protected are handled automatically.

// This will print to the console.
return new Response<ExecutionResponseStatus> (
    ExecutionResponseStatus.InvalidArgs, 
    "Yo, those arguments are whack!"
    );
// This will fail silently
return new Response<ExecutionResponseStatus> (
    ExecutionResponseStatus.InvalidArgs, 
    null
    );

To see available responses, check out ExecutionResponseStatus.