Skip to content

Achievement Creation

Here's how you can create a new in-game achievement.

First step

Before we do any coding we'll have to make sure the achievement will display correctly, otherwise nothing will appear on the list of achievements.

Adding resources for the achievement

In-game text

Depending on how you mod the game (recommended to do addons rather than directly modifying game files) you will have to modify:
resource/<game/addon_name>_<language>.txt

Which can be something like resource/p3_english.txt or addons/my_cool_addon/resource/my_cool_addon_english.txt

Note

If you plan on having multiple language support for your addon, don't forget to also change for other languages like p3_russian.txt

We then just need to add something like this (we chose ACH_CURIOUS_BASTARD for our achievement):

"ACH_CURIOUS_BASTARD_NAME" "CURIOUS BASTARD"
"ACH_CURIOUS_BASTARD_DESC" "Add a new in-game achievement."


In-game icons (locked, progress/finished)

Postal 3 follows the same naming convention like Left 4 Dead 2.
Images are located in materials/vgui/achievements

Since we named our achievement ACH_CURIOUS_BASTARD we would need the following files:
ACH_CURIOUS_BASTARD.vtf
ACH_CURIOUS_BASTARD.vmt
ACH_CURIOUS_BASTARD_LOCK.vtf
ACH_CURIOUS_BASTARD_LOCK.vmt

Look at the other achievement icons from the folder for an example.

Second step

Now that we added the important resources for our achievement, we can add our code.

Hooking onto PostInit

Warning

Achievement creation is only reliable during PostInit, do not attempt creating a new achievement elsewhere since it will not work properly!

1
2
3
4
5
6
// Hook onto PostInit
[HOOK SEngine PostInit]
void PostInit()
{
    CreateNewAchievement("ACH_CURIOUS_BASTARD", 50, false, 50);
}

Now here comes the important part, the class name must have the same name as the achievement itself!

Here's an example how this basic achievement would look like:

class ACH_CURIOUS_BASTARD : IClient
{
    CBaseAchievement@ self;

    int m_iNumEvents;               // must exist
    array<string> m_pszEventNames = // must also exist, you can initialize it here or inside Init
    {
        "npc_death",
        "player_death"
    };

    void Init(CBaseAchievement@ obj)
    {
        @self = @obj;
        m_iNumEvents = m_pszEventNames.size();
    }


    // Actual checks for the achievement here
    void OnEvent(string eventName, IGameEvent@ eventObj)
    {
        if (self.IsFailed() == true)
        {
            return;
        }

        // If player is dead, this achievement is counted forever
        if (eventName == "player_death")
        {
            self.IncrementCount();
        }
        // If an NPC died, this achievement is set as failed until new game
        else if (eventName == "npc_death")
        {
            self.SetFailed(true);
        }
    }
}