Dustscripting Is Live

Dustscript support has been added to Dustmod. Dustscripts allow level authors to make their levels more dynamic by adding their own custom game behavior. Want the player to have to clear the enemies in a room before being able to progress? Dustscript can make it happen.

Dustscript makes use of the AngelScript runtime already used within Dustforce. Each script is run within its own isolated context that communicates with the game script using the Dustscript API. Keeping scripts in their own isolated contexts rather than running them within the game context was necessary to prevent scripts from e.g. sending cheated replays and decoupling scripts from the exact game ABI.

Angelscript

A basic understanding of Angelscript is needed to write Dustscripts. Fortunately Angelscript borrows a lot of syntax from C++. The Angelscript documentation page can be found here. Dustscript relies heavily on object handles in particular so understanding how they work is helpful in understanding how to use Dustscript.

The Dustscript API

The API definitions and documentation will be updated here and here. I will avoid breaking the ABI so that old scripts will continue to work, however there may be some exceptional circumstances where this is necessary while the Dustscript system is still in early development.

In order to help with save states (and multiplayer) your scripts can not make use of non-const globals (you should see an error). Instead you can store all of your state as member variables of your script class. In addition, the following constraints should be followed to ensure your script works for online multiplayer:

  • Do not call draw functions outside of the script.draw() callback.
  • Do not update any state within the script.draw() callback, it should only be a rendering of the current game state.
  • Do not update game state based on information obtained from calls like ‘get_active_player()’. These kinds of calls should just be used for rendering.
  • Do not use uninitialized memory. Angelscript does not automatically zero data. (update: I believe this is no longer necessary but still recommended)

Writing Your First Script

First, after creating a new map make sure to set the level type to “Dustmod”. Only dustmod or nexus level types can have scripts added to them.

Screenshot from 2017-07-27 18-55-30.png

Next, let’s write a quick script to attach to this level. In this script we’re going to award the player their air charges every time we see them collect dust. Get out your favorite editor and save this as the file “sample.cpp” (the cpp extension is useful if your editor supports syntax highlighting since Angelscript is so similar to C++). You can save this file in your level_src directory or in the directory containing the dustmod binary.

const int MAX_PLAYERS = 4;

class script {
  /* We don't actually use the scene object in this script, but most will. */
  scene@ g;

  /* Keep track of how much dust we had in the last frame. */
  array<int> dust_last;

  /* Keep track of how much dust we had when the last checkpoint was saved. */
  array<int> dust_checkpoint;

  script() {
    @g = get_scene();

    /* Initialize each player to have collected 0 dust last frame. */
    for (int i = 0; i < MAX_PLAYERS; i++) {
      dust_last.insertLast(0);
      dust_checkpoint.insertLast(0);
    }
  }

  void step(int entities) {
    for (int i = 0; i < MAX_PLAYERS; i++) {
      /* Try to get a 'dustman' object handle for each player. */
      entity@ e = controller_entity(i);
      if (@e == null) {
        continue;
      }
      dustman@ dm = e.as_dustman();
      if (@dm == null) {
        continue;
      }

      /* If the player exists check its total filth and see how it compares to
       * last frame. If it's increased set their 'dash' (aircharges) back to
       * their 'dash_max' (max aircharges). */
      int dust_cur = dm.total_filth();
      if (dust_cur > dust_last[i]) {
        puts("Restoring Air Charges");
        dm.dash(dm.dash_max());
      }

      /* Note the last seen dust count for thsi player. */
      dust_last[i] = dust_cur;
    }
  }

  void checkpoint_save() {
    /* Save the current dust counts when the checkpoint is saved. */
    dust_checkpoint = dust_last;
  }

  void checkpoint_load() {
    /* Restore the saved dust counts when a checkpoint is loaded. */
    dust_last = dust_checkpoint;
  }
}

Back in the Dustforce client make sure you toggle on the console (by default this is done using F12). Navigate to the scripts tab and enter the script name “sample.cpp” as shown and push enter.

Screenshot from 2017-07-27 19-22-55.png

You should see the console display something like “Compiled 1682 bytes from ‘…'”. Try introducing a compile error into the script and compiling again by pushing Control and clicking the “[x]” button next to the script name, you should see the console display the compile error.

Now you can test out your script. Try adding a tall vertical wall with dust on it as shown. By collecting the dust you should now be able to continue to make air jumps and climb the wall.

Samples

Filth Air Charge (sample above)
Prop Test
Hitbox Test

Security

Dustscripts are run in their own private context and can only access the Dustscript API. Nonetheless the Dustscripting system does open up a large attack surface someone could conceivably try to exploit. If you’d prefer to not to run custom scripts you can check ‘Disable Scripts’ in the Dustmod menu.

Leave a comment