Skip to content

Creating Custom Achievements

Eric Tsai edited this page Jul 25, 2013 · 6 revisions

Table of Contents

Overview

Creating custom achievements on the ServerAchievements engine can be broken down into 3 main steps:

   1. Create achievement pack  
      a. Set pack name  
      b. Define achievements
   2. Implement event handlers  
      a. Update achievement progress    
      b. Unlock achievement   
      c. Notify owner of current progress
   3. Add achievement pack to configuration file

This page will provide examples for each step and will assume the package name is "ExamplePack". Each section will only show code relevant to that section. The final product will an achievement pack containing three achievements: the first triggered upon dying, the second unlocking after landing 30 head shots in a wave, and third unlocking after 25 clots have been killed. The full code is given at the bottom of the page. The Server Achievements Pack page has more complex examples of utilizing the ServerAchievements engine.

This guide assumes you will be extending from AchievementPackPartImpl. While you can extend from AchievementPack, you should only do so if you wish to implement the AchievementPack interface your own way.

Achievement Pack

In most cases, achievement packs should extend from the AchievementPackPartImpl class. The class provides a pack name variable, a data structure for storing achievement information, and functions to update the achievement state. It is important that you do not change the pack name nor achievement ordering once the achievement pack has been released. However, you may change the titles, descriptions, images, and notification increments and add more achievements as you see fit.

class MyAchievementPack extends AchievementPackPartImpl;

Setting Pack Name

The packName variable sets the achievement pack name. Because the name is used to identify each pack, the name must be unique for each achievement pack.

defaultproperties {
    packName= "Example Pack"
}

Defining Achievements

Achievement information is stored in the achievements array, which is an array of Achievement structs. While the Achievement struct contains many member variables, the only relevant ones are: title, description, image, maxProgress, and notifyIncrement. The only required variables are title and description. If no image texture is provided, a check mark will be used as the icon. See the Achievement Pack page for specifics about the Achievement struct and achievements array.

defaultproperties {
    achievements(0)=(title="Sample 01",description="Do something")
    achievements(1)=(title="Sample 02",description="Do something 30 times in a wave",maxProgress=30,noSave=true)
    achievements(2)=(title="Sample 03",description="Do something 25 times, notify owner every 1/5th of the way",maxProgress=25,notifyIncrement=0.20)
}

Event Handlers

The AchievementPack class provides event handlers for several in game events. These event handlers will be utilized to update the achievement state. To update the achievements, use the modifier functions provided in AchievementPackPartImpl. Do not update the information by directly accessing the array

Full documentation of the handlers is available on the Event Handlers page.

Updating Achievement Progress

For progress based achievements require progress persistence, the addProgress function will update the progress, notify the owner if sufficient progress has been made, and unlock the achievement if all requirements have been met.

For progress based achievements that do not save progress between games, the "addProgress" function and "maxProgress" variable can still be used as normal. You simply need to set the "noSave" variable to true in the achievement definition. Setting "noSave" to true will not save the progress after the game has ended nor will it notify the player of any progress updates. You will also need to reset the progress when appropriate, i.e. wave end, reloading, etc.

event killedMonster(Pawn target, class<DamageType> damageType, bool headshot) {
    log("You killed a"@target@"with"@damageType@". Headshot kill?"@headshot);
    if (ZombieClot(target) != none) {
        addProgress(AchvIndex.SAMPLE_02, 1);
    }
}

event damagedMonster(int damage, Pawn target, class<DamageType> damageType, bool headshot) {
    log("You damaged a"@target@"with"@damageType@"dealing "@damage@"damage. Headshot?"@headshot);
    if (headshot) {
        addProgress(AchvIndex.SAMPLE_03, 1);
    }
}

event waveEnd(int waveNum) {
   log("Wave ended"@waveNum);
   achievements[AchvIndex.SAMPLE_03].progress= 0;
}

Unlocking an Achievement

For achievements that do not require progress, the achievement can be unlocked by directly calling the "achievementCompleted" function.

event playerDied(Controller killer, class<DamageType> damageType, int waveNum) {
    log("You died. Killer="$killer @ "damage="$damageType@ "wave="$waveNum);
    achievementCompleted(AchvIndex.SAMPLE_01);
}

If the achievement is already unlocked, the function will do nothing. Otherwise, it will notify the owner with a popup saying he has unlocked the achievement

Notifying the Owner

Both addProgress and achievementCompleted will notify the owner. When an achievement is unlocked or sufficient progress has been made, the owner will received a popup notification on his HUD.

Adding Achievement Pack

Once your achievement pack has compiled, you will need to add it to the mutator's configuration file (ServerAchievements.ini) to load it into the game. The configuration property "achievementPacks" stores a list of packs to load.

[ServerAchievements.SAMutator]
achievementPacks=ExamplePack.MyAchievementPack

Full Sample Code

Copy this code into "MyAchievementPack.uc" and place the class in a package named "ExamplePack"

class MyAchievementPack extends AchievementPackPartImpl;

enum AchvIndex {
    SAMPLE_01, SAMPLE_02, SAMPLE_03
};

function MatchStarting() {
    log("Match is starting");
} 
event matchEnd(string mapname, float difficulty, int length, byte result, int waveNum) {
    log("Match ended: "@mapname@difficulty@length@result@waveNum);
}  
event waveStart(int waveNum) {
    log("Wave started"@waveNum);
}  
event waveEnd(int waveNum) {
   log("Wave ended"@waveNum);
   achievements[AchvIndex.SAMPLE_03].progress= 0;
}
event playerDied(Controller killer, class<DamageType> damageType, int waveNum) {
    log("You died. Killer="$killer @ "damage="$damageType@ "wave="$waveNum);
    achievementCompleted(AchvIndex.SAMPLE_01);
}
event killedMonster(Pawn target, class<DamageType> damageType, bool headshot) {
    log("You killed a"@target@"with"@damageType@". Headshot kill?"@headshot);
    if (ZombieClot(target) != none) {
        addProgress(AchvIndex.SAMPLE_02, 1);
    }
}
event damagedMonster(int damage, Pawn target, class<DamageType> damageType, bool headshot) {
    log("You damaged a"@target@"with"@damageType@"dealing "@damage@"damage. Headshot?"@headshot);
    if (headshot) {
        addProgress(AchvIndex.SAMPLE_03, 1);
    }
}

defaultproperties {
    packName= "Example Pack"
    
    achievements(0)=(title="Sample 01",description="Do something")
    achievements(1)=(title="Sample 02",description="Do something 30 times in a wave",maxProgress=30,noSave=true)
    achievements(2)=(title="Sample 03",description="Do something 25 times, notify owner every 1/5th of the way",maxProgress=25,notifyIncrement=0.20)
}