The VScript Book

Chapter 3.4: Debugging Your Scripts

You will write code, and that code will have bugs. This is a completely normal part of programming for everyone, from beginners to experts. A "bug" is just your script behaving in a way you didn't expect. The skill of a programmer is not in writing perfect code the first time, but in being a good detective—finding out *why* the code is misbehaving and fixing it. This process is called debugging.

Debugging 101: The printl Detective

The simplest and most powerful debugging tool you have is printl() ("print line"). The basic idea is to make your script report its status to the console as it runs. You can print out the value of variables at key moments to see if they are what you expect them to be.

Example: Finding the bug in our 3-press door.

Imagine your "3-press unlock" door isn't working. Why? Is the function not being called? Is the counter not going down? Let's add some `printl` statements to find out.

// In the door's script:
unlockPresses <- 3

function InputUnlock() {
    // 1. Is this function even running?
    printl("InputUnlock was called!")

    // 2. What is the value of the counter BEFORE we change it?
    printl("Before decrement, unlockPresses is: " + unlockPresses)
    
    unlockPresses-- // Decrement the counter
    
    // 3. What is the value AFTER we change it?
    printl("After decrement, unlockPresses is: " + unlockPresses)
    
    if (unlockPresses <= 0) {
        printl("Condition met! Door should unlock.")
        return true
    }
    else {
        printl("Condition NOT met. Door should remain locked.")
        return false
    }
}

By watching the console output each time you press the button, you can trace the logic step-by-step and see exactly where things are going wrong. This method solves 90% of common scripting bugs.

Live Testing: The Developer Console

The developer console is your live laboratory. You can run Squirrel code directly in the game using the script <code> command. This is perfect for instantly testing a single line of logic without having to recompile your map.

Examples

Type these into the developer console while your map is running:

// Prints a message to the console.
script printl("Hello from the console!")

// Gets a handle to the player and prints their current X, Y, Z coordinates.
script p <- GetPlayer()
script printl(p.GetOrigin())

// Finds an entity named "my_door" and fires its "Open" input.
script { door <- Entities.FindByName(null, "my_door") } { EntFireByHandle(door, "Open", "", 0, null, null) }

Tip: The console treats the entire line as one command, so you cannot use semicolons ; to separate statements. However, you can wrap statements in curly braces { } to execute multiple commands in a sequence:
script { p <- GetPlayer() } { printl(p.GetOrigin()) }


Warning: Do not use local to declare variables you want to use in a later command. To create a persistent variable from the console, you must use <-. Be aware that this creates the variable in the global scope (the root table).
script p <- GetPlayer() is the same as writing script ::p <- GetPlayer().

Visual Debugging: Drawing in the World

Sometimes, printing text is not enough. To debug spatial problems—like checking an entity's boundaries or visualizing a path—you need to see your logic in the game world. VScript provides several `DebugDraw` functions for this purpose.

Drawing Bounding Boxes and Lines

These functions draw simple shapes that are only visible when `developer` is set to 1 or higher in the console. They are perfect for temporarily visualizing positions and areas.

// A function to visualize an entity's collision box.
function DebugShowEntityBounds(entity) {
    if (entity == null || !entity.IsValid()) return;
    
    local origin = entity.GetOrigin();
    local mins = entity.GetBoundingMins();
    local maxs = entity.GetBoundingMaxs();
    
    // Draw a red box around the entity that lasts for 10 seconds.
    // Color is R, G, B, Alpha. Duration is in seconds.
    DebugDrawBox(origin, mins, maxs, 255, 0, 0, 100, 10.0);
}

// A function to visualize a path between two points.
function DebugShowPath(startPos, endPos) {
    // Draw a green line that lasts for 10 seconds and is not depth-tested.
    DebugDrawLine(startPos, endPos, 0, 255, 0, true, 10.0);
}

Controlling the Console from a Script

You can also send commands directly to the console from your script using `SendToConsole()`. This is useful for enabling debug modes or changing cvars for testing purposes.

function EnableDebugMode() {
    // Enables developer mode to see debug overlays.
    SendToConsole("developer 1");
    
    // Enables god mode for the player for testing.
    SendToConsole("god");

    printl("Debug mode enabled!");
}

// 'developer 1' shows basic debug info.
// 'developer 2' shows more verbose info, including entity I/O events.

By combining `printl`, console commands, and debug drawing, you have a complete toolkit for diagnosing almost any bug in your scripts.

Advanced: The Real-Time Debugger

What if you could pause time inside your script? That's what a full-fledged debugger lets you do. For really complex scenarios, like when you're creating a large library of code or tracking down a very tricky bug, you can connect the game directly to your code editor.

This is made possible by a Visual Studio Code extension called VScript Debugger. It allows you to:

  • Set Breakpoints: Mark a line in your code to make the game freeze the moment it's executed.
  • Step Through Code: Execute your paused script one line at a time, watching the effects in-game.
  • Inspect Variables: When paused, you can see the current value of every variable in your script's memory.

This is an advanced technique and can be a bit unstable with Portal 2's older VScript engine. However, for creating truly complex logic like that found in PCapture-Lib, it is an invaluable tool. The setup generally involves installing the extension, running the script_debug command in the game console, and then "attaching" your code editor to the game.