Getting started

Note

  • This tutorial also applies to Catharsis Reborn
  • Postal 3 (pre-ZOOM/ZOOM version) does not have AngelScript! (get Angel mod from here: ModDB)

Getting script files to load

Head over to ../p3/scripts/AngelScript/ and open !as_scripts.txt
You'll see a bunch of files have already been included:

// This number is looked for when loading save file, you can up this number everytime you make heavy changes to the codebase
// If before and after numbers don't match, the Serializer will automatically abort when attempting to restore values
// NOTE: This absolutely must be an integer!
const int AS_MOD_VERSION = 0;

#include "engine.as"
#include "npc.as"
#include "player.as"

Typically here we only want to use #include and no other code here, but there's nothing stopping you from actually putting code in there, it just won't look nice!

Start the game, load into a map, then alt TAB out

AngelScript can be recompiled while the game is running, you don't need to reload the game itself! (normally AngelScript will only compile once per game)

Creating a working example from scratch

In the AngelScript folder where !as_scripts.txt is located create a file called example.as
Then open !as_scripts.txt and include it like so:

#include "example.as"

If you want to put this file into a folder, you can also do that like so:

#include "myfolder/example.as"

Just remember if you want to include other files (inside example.as) you will need to navigate back like so:

// This would be "../p3/scripts/AngelScript/utils.as"
#include "../utils.as"
// This would be "../p3/scripts/AngelScript/myfolder/tools.as"
#include "tools.as"

open "example.as"

Now we'll add a very simple class that will only print console messages, the comments will explain everything:

// If you want to take advantage of cache and script object functions, you need to
// inherit from IPostal3Script, otherwise it won't work!
class MyCustomClass : IPostal3Script
{
    // A simple variable, this is what we'll refer to the entity 
    // (from their viewpoint they are "self")
    CP3SObj@ self;

    // This can store numbers, crazy right?
    int num;

    // The constructor, this is where you can set up variables
    // This function is always the class name, don't also forget about the CP3SObj@ param!
    // Note: if the class will be both global and non-global then make sure to check obj is not null!
    MyCustomClass(CP3SObj@ obj)
    {
        //if (@obj != null)
        @self = obj;

        // Start at 0, better safe than sorry
        num = 0;
    }

    // We'll call this in Postal3Script, you can add parameters but P3S does not support this
    // you can still call this function in other functions if you wanted to!
    void VerySimpleFunction()
    {
        // This is the same as "num = num + 1"
        num++;

        // Note: If you aren't too familiar with Printf, here's this cheat sheet
        //  int : %d
        //  string : %s
        //  float : %f
        //  Break line : \n
        // Using the wrong character might result in corrupted text or crash, always double check :)

        // Now we'll print this variable's value to the console
        Printf("VerySimpleFunction: %d\n", num);
    }
}

Now head back into the game and enter wj_angelscript_recompile
If everything went smoothly then you should not see any ERROR messages.

Debugging

Now that we have compiled the code we want to make sure it really works.
Head back into the game and enter the following:
wj_angelscript_exec MyCustomClass VerySimpleFunction

This should have printed
VerySimpleFunction: 1
to the console if everything went smoothly.
Now if you spam this command the number should go higher and higher, that means it works just fine!

Note

wj_angelscript_exec will execute functions GLOBALLY

Postal3Script

Now that we know we didn't mess up anything with copy and pasting, we'll modify the Player's AI to execute VerySimpleFunction() from AS every second.

Head into the scripts folder and find ai_player.p3s

Scroll down and look for xpt_OnPrimaryAttack

Put this before IfAttr "Timer:tPrimaryAttack > 0 Return 1":

            // target   <class>     <function>
AngelScript "Object:self MyCustomClass VerySimpleFunction"

It should look something like this:

xpt_OnPrimaryAttack
{
    actions
    {
        AngelScript "Object:self MyCustomClass VerySimpleFunction"
        IfAttr "Timer:tPrimaryAttack > 0 Return 1"
        Timer tPrimaryAttack,3
        //IfAttr "ea_ActiveWeapon >= 5 Block begin"
            //ExecutePattern .xpt_SaveStatus
            //SetAttr "ea_status sKILLER"
            //Timer tKiller,20
        //Block end

        //Brutal primary attacks
        IfAttr "ea_armed == WPN_GRENADE     ExecutePattern .xpt_OnBrutalAct"
        IfAttr "ea_armed == WPN_MOLOTOV     ExecutePattern .xpt_OnBrutalAct"
        IfAttr "ea_armed == WPN_BEES        ExecutePattern .xpt_OnBrutalAct"

        //Wrong primary attacks
        IfAttr "ea_armed == WPN_GAS         ExecutePattern .xpt_OnWrongAct"
        IfAttr "ea_armed == WPN_SPRAY       ExecutePattern .xpt_OnWrongAct"
        IfAttr "ea_armed == WPN_CATS        ExecutePattern .xpt_OnWrongAct"
        IfAttr "ea_armed == WPN_CATNIP      ExecutePattern .xpt_OnWrongAct"
        IfAttr "ea_armed == WPN_TASER       ExecutePattern .xpt_OnWrongAct"
    }
}

Recompile AngelScript with wj_angelscript_recompile and reload the level by entering reload
Now everytime you shoot, the number should increase everytime.

Object:self can always be replaced with GLOBAL, but then that class would be shared with other NPCs or Players, everytime they would shoot, the number would go up, they would not have their "own" number.

You can tinker with this by doing similar experiments BUT with NPCs instead.