Minecraft Forge: Creating Custom Achievements

Introduction


Achievements are basically player stats that can accumulate in order.  They are often related to accomplishing something new, for example simply opening your inventory for the first time in each new game is considered an achievement.  They can be a progression, like levels.

It is fairly easy to make achievements for your mods, and even edit the vanilla achievements.

If You Want To Remove or Replace Vanilla Achievements


You may wish to remove the vanilla achievements. In this case you need to both remove them from the achievements GUI as well as prevent them from executing.

The overall list of achievements, including those you register, is in a public LinkedList field in AchievementList class called achievementList. So in the init handling method of your common proxy, before you register your own achievements, you could clear() this list, or remove() specific listings (all the methods for standard LinkedList are available). Then register your own. That will remove them from the GUI.

But they will still fire because the achievement instances are called directly rather than being looked up in the list, sprinkled throughout the code. Luckily there is an AchievementEvent that you can handle. (Thanks to theskyisbluetoday for this tip.) You can check for the achievement type and cancel the event if desired. For example, this would cancel the achievement for opening your inventory:

@SubscribeEvent
public void onEvent(AchievementEvent event) 
{
    if (event.achievement.equals(AchievementList.openInventory)) 
    {
        event.setCanceled(true);
    }
}

Initialize Fields For Custom Achievements In Your Main Mod Class


To refer to the instances of each achievement, it is best to create public static Achievement fields in your main class.  For example:

public static Achievement achievementStartMagicBeans = 
      new Achievement("achievement.startmagicbeans", "startmagicbeans", 
      0, 0, MagicBeans.magicBeans, (Achievement)null);
public static Achievement achievementGiantSlayer = 
      new Achievement("achievement.giantslayer", "giantslayer", 
      2, 1, MagicBeans.bootsOfSafeFalling, 
      MagicBeans.achievementStartMagicBeans).setSpecial();

I believe the first string is a key for the map data structure, the second string is important as it is the unlocalized name of the achievement (your lang file will associate this with the text for the achievement), the next two int parameters give the position that the achievement will be displayed on the achievement page, the Item parameter is for the icon to use for the achievement, and the last Achievement parameter(s) are any achievements that must be completed first.

Warning: If you're using your own custom items for the icon parameter, make sure the item is instantiated first before instantiating the achievement field that uses it.

Register The Custom Achievements


In your common proxy class, in the init handling method, you need to register the achievements.  For example:

MagicBeans.achievementStartMagicBeans.registerStat(); 
MagicBeans.achievementGiantSlayer.registerStat(); 

Pretty simple!  Just call each achievement's own registerStat() method.

Register The Custom Achievement Page


You also need to register an achievement page.  For example:

AchievementPage.registerAchievementPage(
      new AchievementPage("Magic Beans Achievements", 
            new Achievement[] {MagicBeans.achievementStartMagicBeans, 
            MagicBeans.achievementGiantSlayer}));

This is pretty self explanatory, basically you register a page, giving it a name and an array of your achievements.

Put The Text For Your Custom Achievement In The .lang File


To aid localization (meaning playing the game in different languages), the text for the achievement is looked up from the .lang file(s).  So for the achievements in the examples above, I put this in my .lang file:
achievement.startmagicbeans=Started Jack and Beanstalk Adventure
achievement.startmagicbeans.desc=Feed a golden carrot to a cow
achievement.giantslayer=Giant Slayer
achievement.giantslayer.desc=Find a giant and kill it

Note that you should put a lang entry for both the title of the achievement as well as a description.

Trigger The Custom Achievements


Each achievement basically has a boolean value (actually an in that is 0 or 1) to indicate whether it has been achieved.  You just need to set that wherever it makes sense in your mod's code, such as in an event handling method.  But the achievement has to be associated to a particular player, so you need to trigger the achievement somewhere where the player is known.

For example, in my mod you get the "Giant Slayer" achievement when you kill my Giant entity.  So in my EntityGiant class, in the attackEntityFrom() method I put the following code:

if (getHealth() <= 0.0F)
{
    onDeath(damageSource);
    if (entityAttackedBy instanceof EntityPlayer)
    {
        ((EntityPlayer)entityAttackedBy)
              .addStat(MagicBeans.achievementGiantSlayer, 1);
    }
}

You can see that I have the player instance that attacked the Giant so I just have to add the stat with a value of 1.

Interesting Idea -- Displaying But Not Triggering Achievements


It is possible you might want to display another message in a way similar to how achievements are displayed. You can do that by creating them normally and then (only on the client side, use your proxy!) using  Minecraft.getMinecraft().guiAchievement.displayAchievement(yourAcheivementHere).

Displaying Icon That Isn't An Item

Achievements use item textures as a symbol to display. So if you want to use custom symbols that might not be an item, you should create a custom item that has the texture you want and use that.

Conclusion


Achievements are pretty easy to add.  Have fun.  If you have any comments or corrections to this tutorial please don't hesitate to contact me.

4 comments:

  1. I had an issue where I was trying to trigger an onCraft event for my custom item. It worked for my item, but not for a block that extends blockpane (think iron bars). When I craft, it never runs the trigger. But using the same crafting trigger for crafting a tool works. Any thoughts on how to trigger a craft event of blockpane?

    ReplyDelete
  2. It is hard for me to guess what is wrong without seeing your code. If an event isn't firing at all, I think it is likely that you haven't registered the event handler. I don't think it should make any difference if you're crafting with a custom block unless your event handling code doesn't check for it properly.

    ReplyDelete
  3. Hey there, old post but still quite active, I guess. I used it as reference.

    Is it possible to actually remove an achievement? I might have a use case and struggle with it. Setting it to nil per addStat method didn't work for me.

    ReplyDelete
    Replies
    1. Did you read my whole post. I discuss how to remove an achievement. You can remove it from the GUI and also you can intercept it with an event handler.

      Delete