0 votes
by (220 points)
How can I make achievements for Harlowe? Thanks.

2 Answers

0 votes
by (160 points)
I don't know where you're displaying your achievements, but couldn't you create a passage (navigable from your menu or whatever), and print achievements only when certain variables, triggered in the story, are satisfied?
+1 vote
by (63.1k points)
edited by

Preamble

A "real" achievement system implies that users have profiles, and can log in, and that you can authenticate them and bind these to their accounts/profiles.  That is possible to do in (or more accurately, around) Twine, but requires you to have a server, and probably goes beyond the scope of this Q&A.

Harlowe has no built-in way to save values to localStorage, unlike SugarCube and the Twine 1 formats.  Adding custom JavaScript code to Harlowe in specific is always dicey because actually getting it to cooperate with Harlowe's passage code requires some hacking and abuse due to the format's lack of a JavaScript API.  So strap in, I guess.

The Code

Add the following code to your story JavaScript area. It creates a new namespace called Chapel, and several properties and methods for achieving what you want to do by leveraging localStorage. It should be noted that this is a sort of imitation of an achievements system rather than an actual achievements system. Users will not be able to share achievements between devices, for example.

window.Chapel = window.Chapel || {};
(function () {
    var storage = window.localStorage || false;
    var achievementKey = 'tw-achievement';
    var achievements = [];
    
    function setItem (key, val) {
        try {
            if (storage) {
                storage.setItem(key, JSON.stringify(val));
            } else {
                throw new Error('Local storage is inaccessable.');
            }
        } catch (err) {
            console.warn(err);
            alert(err.message);
        }
    }
    
    function getItem (key) {
        try {
            if (storage) {
                return JSON.parse(storage.getItem(key));
            } else {
                throw new Error('Local storage is inaccessable.');
            }
        } catch (err) {
            console.warn(err);
            alert(err.message);
        }
    }
    
    function removeItem (key) {
        try {
            if (storage) {
                storage.removeItem(key);
            } else {
                throw new Error('Local storage is inaccessable.');
            }
        } catch (err) {
            console.warn(err);
            alert(err.message);
        }
    }
    
    function achievementAdd (name) {
        achievements.push(name);
        setItem(achievementKey, achievements);
    }
    
    function loadAchievements () {
        var arr = getItem(achievementKey) || [];
        achievements = arr;
        return arr;
    }
    
    function clearAchievements () {
        achievements = [];
        removeItem(achievementKey);
    }
    
    function printAchievements (str) {
        return achievements.join(str || '\n');
    }
    
    Chapel.storage = Chapel.storage || {
        set : setItem,
        get : getItem,
        del : removeItem,
    }
    
    Chapel.achievements = Chapel.achievements || {
        add   : achievementAdd,
        load  : loadAchievements,
        clear : clearAchievements,
        print : printAchievements
    }
    
}());

Easier to copy version.

Usage

In a startup-tagged passage, you'll need to place code like this:

(set: _dummy to Chapel.achievements.load())

This abuses the (set:) macro to run our custom method for loading achievements. Achievements will be loaded from local storage, or a new array for them will be set up.

To add an achievement, use code like this:

(link: 'Get a new achievement')[
	(set: _dummy to Chapel.achievements.add('YOU ARE A NICE PERSON'))
]

Again, we abuse the (set:) macro, this time calling our add() method. The string passed as an argument ('YOU ARE A NICE PERSON' in the above example) is the name of the achievement.

To show the user a list of their unlocked achievements:

(print: Chapel.achievements.print())

If you want your achievements to be separated by commas instead of newlines, try something like this instead:

(print: Chapel.achievements.print(', '))

 

...