SkillAgentSearch skills...

Ppmod

VScript library for rapid, comfortable prototyping of Portal 2 mods

Install / Use

/learn @p2r3/Ppmod
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ppmod

VScript library for rapid and comfortable prototyping of Portal 2 mods.

The focus of this project is to provide tools that assist in developing Portal 2 VScript mods faster and much more comfortably than through vanilla VScript. This involves adding syntactic sugar, new features, employing various workarounds for missing features or fixing broken ones through entities often specific to Portal 2. While ppmod strives to be performant, this does not come at the cost of reduced ease of use.

In other words, ppmod makes Portal 2's Squirrel feel less like a cheap hack, and more like a native game interface.

Installation

Since ppmod version 4, the environment is expected to be as clean as possible, without any instantiated entities or vectors. This can be achieved by placing ppmod.nut into scripts/vscripts and calling it at the very top of mapspawn.nut, after making sure that the current scope has server-side control:

  // File: "scripts/vscripts/mapspawn.nut"

  if (!("Entities" in this)) return; // Quit if the script is being loaded client-side
  IncludeScript("ppmod"); // Include ppmod as early as possible

Including ppmod in an already populated environment will still work at the cost of additional vector metamethods and entity method abstractions - that's the syntactic sugar mentioned earlier. This will get logged to the console as a warning, but is technically harmless if these features are unused.

Getting started

Setting up and working with an environment like this for the first time can be overwhelming, so here's some boilerplate to help you get started.

This script will spawn a red cube in front of the player's head, and make it print to the console once it gets fizzled. This should provide a solid example that you can then play around with to get an idea of what it's like to work with ppmod.

  // File: "scripts/vscripts/mapspawn.nut"

  if (!("Entities" in this)) return;
  IncludeScript("ppmod");

  // This function is called whenever a map is fully loaded
  // We wrap it in async() to make it more comfortable to use asynchronous functions inline
  ppmod.onauto(async(function () {

    // Retrieve additional player info, like eye position and angles
    local pplayer = ppmod.player(GetPlayer());
    yield pplayer.init();

    // Props cannot be created with the CreateByClassname method, so we use ppmod.create instead
    yield ppmod.create("prop_weighted_cube");
    local cube = yielded;

    // Teleports the new cube to 64 units in front of the player's eyes
    local pos = pplayer.eyes.GetOrigin() + pplayer.eyes.GetForwardVector() * 64;
    cube.SetOrigin(pos);
    // Colors the cube red with the "Color" input
    cube.Color("255 0 0");
    // Connects a script function to the cube's "OnFizzled" output
    cube.OnFizzled(function () {
      printl("The red cube has been fizzled!");
    });
    // For other inputs/outputs, refer to: https://developer.valvesoftware.com/wiki/prop_weighted_cube

  }));

Global utilities

These are essential utility functions, classes and methods that Portal 2's implementation of Squirrel and VScript should (arguably) have by default. This includes abstractions and shorthands of already existing features.

min / max

Returns the smallest / largest of two values.

  min(0.5, 2) // Returns 0.5
  max(0.5, 2) // Returns 2

round

Rounds a number to the specified precision in digits after decimal point, 0 by default.

  round(1.2345) // Returns 1.0
  round(1.2345, 2) // Returns 1.23
  round(5.5) // Returns 6

Vector methods

Component-wise multiplication

  Vector(1, 2, 3) * Vector(2, 4, 8) // Returns Vector(2, 8, 24)
  Vector(1, 2, 3) * 2 // Still returns Vector(2, 4, 6)

Component-wise division

  Vector(1, 2, 3) / Vector(2, 4, 8) // Returns Vector(0.5, 0.5, 0.375)
  Vector(1, 2, 3) / 2 // Still returns Vector(0.5, 1, 1.5)

Equality check

  Vector(1, 2, 3).equals(Vector(1, 2, 3)) // Returns true
  Vector(1, 2, 3).equals(Vector(4, 5, 6)) // Returns false

More useful string output

  "The vector is: " + Vector(1, 2, 3) // Returns "The vector is: Vector(1, 2, 3)"

Fixed KV string output

  "The vector values are: " + Vector(1, 2, 3).ToKVString() // Returns "The vector values are: 1 2 3"

Inline normalization

  Vector(10, 5, 10).Normalize() // Returns Vector(0.666667, 0.333333, 0.666667)
  Vector(10, 5, 10).Normalize2D() // Returns Vector(0.894427, 0.447214, 0)

Extended array class

The pparray class implements some additional array features not present in Portal 2's version of Squirrel. It can be initialized by either providing a size for a new array (and optionally a value to fill it with) or an existing array.

  local arr = pparray([1, 2, 3]);

More useful string output

  printl(arr) // Prints "[1, 2, 3]"

pparray.join

  arr.join(" - ") // Returns "1 - 2 - 3"

pparray.indexof

  arr.indexof(2) // Returns 1, which is the index of the first element with value 2.

pparray.find

  arr.find(function (a) {return a >= 2}) // Returns 1, which is the index of the first element to pass the compare function.

pparray.includes

  arr.includes(4) // Returns false, because the array does not contain the value 4.

Extended heap class

The ppheap class implements a priority queue data structure using a heap. It supports basic heap operations such as inserting elements and retrieving the top element. The heap can be initialized with a maximum size and an optional comparator function. The default comparator constructs a min-heap.

  local heap = ppheap(10, function(a, b) { return a > b }); // Constructs a max-heap

ppheap.insert

  heap.insert(5)
  heap.insert(3)
  heap.insert(10)
  heap.size // Holds the heap size, which is now 3

ppheap.gettop

Retrieves the top element of the heap without removing it

  heap.insert(10)
  heap.gettop() // Returns 10
  heap.remove()
  heap.gettop() // Throws "Heap is empty"

ppheap.remove

  heap.insert(10)
  heap.remove() // Returns 10
  heap.remove() // Throws "Heap is empty"

ppheap.isempty

  heap.isempty() // Returns true if heap is empty, false otherwise

ppheap.bubbledown

  heap.insert(5)
  heap.insert(3)
  heap.insert(10)
  heap.bubbledown(2) // Sifts down the element at index 2 (10) to its correct position in the heap

Extended string class

The ppstring class implements some additional string features not present in Portal 2's version of Squirrel. It can be initialized without arguments or by providing an existing string.

  local str = ppstring("Hello world!");

str.split

  str.split(" ") // Returns ["Hello", "world!"]

str.replace

  str.replace("l", "L") // Returns "HeLLo worLd!"

str.includes

  str.includes("world") // Returns true

ppromise

Implements a JavaScript-like "promise" system for working with asynchronous operations.

  ppromise(func)

In Portal 2, there are numerous mutually unsyncronised threads, which can be a hassle to work with. Namely, console commands and entity actions are the most common offenders in generating asynchronous code. Historically, ppmod has used callback functions to accomodate for this, but since version 4, a "thenable" system was established for clarity and consistency.

Setting up a basic ppromise means wrapping a function with the ppromise constructor, which returns a ppromise instance. (Internally, these aren't actually classes or objects due to a workaround for a bug in Portal 2's Squirrel runtime.) Here is an example of a simple promise that resolves in 5 seconds:

  local wait = ppromise(function (resolve, reject) {
    ppmod.wait(function ():(resolve) {
      resolve("5 seconds have passed");
    }, 5);
  });

There are several ways of obtaining the result of a ppromise. The simplest by far is to attach a script to the then, except, or finally methods:

  // Prints "5 seconds have passed".
  wait.then(function (result) {
    printl(result);
  });

  // Prints either the value given to reject(), or any errors caught by the ppromise.
  wait.except(function (err) {
    printl(err);
  });

  // Called when the promise resolves, regardless of outcome
  wait.finally(function () {
    // perform cleanup, etc...
  });

Any number of functions can be attached to each output. Only one of then or except gets called (whichever is encountered first), while finally gets called regardless.

You can also get the value and state of a ppromise directly, though this isn't recommended:

  wait.state // One of "pending", "fulfilled", or "rejected"
  wait.value // The value passed to either resolve() or reject()

Lastly, the value of a ppromise can be resolved inline via async functions.

async

To improve code clarity and reduce nesting, ppmod implements JavaScript-like async functions, which can resolve ppromises inline using the yield keyword, which in this case works similarly to JavaScript's await.

  async(func)

The simplest way of declaring such a function is to wrap it in async(). Here is an example of such a function that waits for ppmod.create to spawn an entity:

  local createCube = async(function () {

    yield ppmod.create("prop_weighted_cube");
    local cube = yielded;

    cube.SetOrigin(Vector(...));
    ...

  });

  // Can be called like a normal function
  createCube();

There are some important things to note here. Firstly, for context, here ppmod.create returns a ppromise that resolves to the created entity's handle

Related Skills

View on GitHub
GitHub Stars32
CategoryDevelopment
Updated5d ago
Forks5

Languages

Squirrel

Security Score

75/100

Audited on Mar 23, 2026

No findings