Plan for achievements
This page documents my notes on the implementation of Achievements. The contents of this page should be considered NON-CANON and subject to revision at any moment (including this one) (or this one).
These details are based on a post made by TeeEmCee to the mailing list a few years ago:
The last few days I've been really busy with other stuff, but I'm working on fixing joysticks now. I'm also thinking ahead to achievements. I figure even if they were only partially done by the time KBB is released, it might be good to at least be able to track them so that players can earn achievements even if they're not aware of them. Step one is to let you define achievements and earn them. I figure there should be three ways to do so: -as a textbox conditional (triggered after the textbox advances, so it isn't shown over the textbox) -via script commands -by setting tags. The achievement would stay unlocked if the tags are later changed back. (Note that this means that the achievement could be unlocked during a battle or while you're inside some menu where scripts can't normally run Step two is integration with steam. Additionally, progress on achievements can be downloaded from steam, so that it's shared between multiple computers. But that can be implemented later. Step three is an alternative way to show achievements in-game if the game isn't being run via steam. That should be pretty straightforward. But there should also be a new menu to show a list of achievements you've unlocked, which is more work. I guess there are two types of achievements: those unlocked directly, and those which have a progress bar. Also, steam lets you fetch info about achievements such as their description and image, but I think we'll probably ignore that and require duplicating that info inside Custom. (If steam is handling the display of achievement popups, it won't be needed anyway)
Background[edit]
What are achievements?[edit]
Achievements are indicators of a player's progress through the game and/or records of special feats performed. The nature of achievements is highly varied and different games will have different desires. Common types of achievements are:
- Milestone progress through a game (eg, completed the first level)
- Accomplishing a difficult task (eg, collecting all types of items)
- Doing something unusual or funny (eg, finding all ways to die outside of battle)
There are probably other classifications, but in essence they all boil down to flags that, once the relevant condition is met, will be marked on the player's profile. Typically achievements are not tied to a specific playthrough of a game, and earned acheivements remain even if a new game is started. However, the conditions to earn an achievement are typically restricted to a single playthrough. Eg, "defeat 100 goblins" must typically be done in the same save game.
This suggests another use case, which is that achievements may not be a binary "they did the thing or not". Using the "defeat 100 goblins" example, it will require keeping track of how many goblins have been defeated. A game may wish to expose the current progress at intervals ("defeat 100 goblins: 20/100") to motivate the player to continue progresing the achievement.
Finally, achievements tend to have amusing names, typically a play on words related to the subject of the achievement. For example, an achievement for finding all the hidden feathers might be named "Birds of a Feather" or similar.
How will achievements work?[edit]
Based on the information from the previous section, an achievement has three sets of important data:
- The achievement definitions (name, icon, etc), baked into the game
- The in-progress data (progress, etc), retained in the save file
- The completed achievements, stored in the user's profile
Initially, achievements will be tied to tags. By default, an achievement will be of type "flag", which means we care about tags being enabled. At their simplest, you might say "if Tag 6 is toggled on, then reward this achievement". Then, you might have a text box that enables Tag 6. As soon as that happens, the game will trigger the achievement.
A more complicated example is that you might have several conditions that must be met for the achievement to trigger. For example, you might need multiple tags enabled at once (eg, "Bob was rescued", "James was exorcised" and "TMC was defeated"). Achievements will let you define multiple tags that all must be enabled at once in order to trigger. Optionally, you may say that the tags "latch", which instead means "all these tags must have been enabled at least once, but not necessarily at the same time".
Finally, you may also wish to use plotscripting to reward an achievement. In this case, you don't need to use any tags, and can instead just use the give achievement command directly.
Getting even more complicated, you might have a condition that can be repeated. Eg, a Tag is enabled and disabled multiple times. Latching won't work, since you need to track each individual enabling. For this, you can switch the achievement to the "count" type. This allows you to define a few other properties, like a target value. The player's value for an achievement can be incremented either with tags or via a scripting command, increment achievement. Once the value reaches the target, then the achievement will trigger.
What about Steam?[edit]
Steamworks integration will be present on day one. If Steamworks support is enabled, you will be able to associate the Steam ID of each achievement. Then, when achievements are awarded, they will be synced with Steam. This is optional, and if Steamworks support is added to a game at a later time, previously rewarded achievements will be synced on the next game launch.
Steam support will 100% work on Windows, but Linux and Mac support are expected to come as well.
What about other platforms?[edit]
Similar features on other platforms (Xbox achievements, Playstation trophies, etc) would be nice but are not currently being planned for.
Will the OHRRPGCE have it's own Achievement display?[edit]
Likely, yes, in time. Achievements added now will be tracked behind the scenes invisibly, and when the OHR can display its own achievements they will be on full display.
Data formats[edit]
Achievement definitions[edit]
Achievement definitions will be stored in the acheivements.reld lump.
- achievements - root node
- next_id integer - the id to be assigned to the next achievement created
- permanent' flag - whether achievements are global (eg, as in Steam) or per-save file (eg, as in Minecraft)
- achievement integer - value is the id of the achievement
- id int - an incrementing id
- name string - name of the achievement (eg, "The Route of all evil")
- type string - what kind of achievement is it ("flag" versus "count")
- max_value integer - the target value which must be reached in order to trigger the achievement
- progress_interval integer - How often to give an update on the achievement progress (0 = never, 1 = every increment, 2 = every other, etc)
- latching flag - Whether the achievement remembers which tags have or have not been enabled.
- tags container - a list of tags that are tracked by this achievement
- tag integer - the tag id
- steam_id string - the steam id of the achievement (eg, "ACH_ROUTE_EVIL")
In progress data[edit]
Achievement progress will be saved in the save game, under a new node named achivememts. Only achievements with meaningful progress will have a presence here. That is, if an achievement either is a "count" type (to store the value), or is "latching" (to store the tags)
- achievements - fake root, contains a tag for each tracked achievement
- achievement integer - value is id of the achievement
- rewarded flag - whether the achievement has been rewarded
- rewarded_date float - timestamp when the achievement was rewarded
- str string - the rewarded date in human-readable format (yyyy mmm dd hh:mm)
- value integer (optional) - the current value of the achievement
- tags container (optional) - which latched tags have been seen
- tag integer - a specific seen tag
- achievement integer - value is id of the achievement
Permanent data[edit]
Rewarded achievements will be saved in [Persist.reld], which is global but specific to a single RPG. Note: this data will only be present if it is meaningful. That is, at least one permanent achievement has been rewarded. If achievements are not permanent, or none have been rewarded, this whole section will be missing.
However, once this section has been created, it will not be removed even if achievements are made non-permanent.
Note: at some point there will be a debug tool to remove them
- achievements - root
- achievement integer - the id of the rewarded achievement
- rewarded_date float - timestamp when the achievement was rewarded
- str string - the rewarded date in human-readable format (yyyy mmm dd hh:mm)
- rewarded_date float - timestamp when the achievement was rewarded
- achievement integer - the id of the rewarded achievement
Beta Testing[edit]
So, you want to try adding achievements to your game. Here's what you need to know:
Prerequisites[edit]
First and foremost, at the moment there is no UI for achievements in-game yet. That means that unless you are planning to release on Steam or another compatible platform, then players are not going to know they got any achievements. That is not to say you can't add them now, eventually we will add UI. But, we don't know the timeline yet.
Second, the only supported platform is Steam. We are hoping in the future to integrate with other platforms (Epic Games launcher seems likely, maybe some Android stuff? I don't know the landscape there, tbh) but those are down the road. On the bright side, we do support all platforms that Steam does, Windows, MacOS and Linux. You must be a register Steamworks developer with an App ID to integrate. The process for doing this is beyond the scope of this document, but know that there are contracts and a $100 USD fee.
Finally, there is no editor in CUSTOM for setting this up. We are working on it, but it may not be ready for release, and doesn't exist at all at time of writing, so you'll have to set a few things up manually for now.
Setup Guide[edit]
1. Setting up Steamworks[edit]
You need to do two things to get Steamworks to function. First, you need to provide the Steamworks libraries. These are included in the steamworks redistributable package in the redistributable_bin directory. You must put the appropriate library next to game.exe, per the platform. That means steam_api.dll (32-bit version, not 64-bit) on Windows, libsteam_api.so (32 or 64 bit as appropriate) on Linux and libsteam_api.dylib on MacOS. If the library is missing, then we will not be able to initialize Steamworks.
If the library is provided correctly, then in g_debug.txt you should see the text Steam initialized early on. If it can't find the library, it will instead say Steam not initialized.
The other thing you need to do is create a file, steam_appid.txt, that contains your App ID. If you don't have an app id, you can use a test game's app id, 480, but you must not distribute your game with this ID.
Note: in theory, you are not meant to distribute steam_appid.txt at all, even if it has a real id, but I don't think this is a hard and fast rule.
If everything is working properly, then when you launch game.exe, you should see the steam overlay come up, and Steam should show you as playing your game (or Space Wars if you used the test ID).
Note: in some circumstances, you have to additionally create a shortcut in Steam to launch game.exe for the overlay to work. We are not really sure why this is, but try it if it is not otherwise working.
2. Creating your achievements[edit]
As mentioned earlier, there is no editor. So, you have to define them manually. The easiest way to do this is to create an XML file with the appropriate data, and then translate that into the file that the game needs. The XML should look like this:
<achievements> <next_id>4</next_id> <achievement> 1 <name>Finished the Job</name> <type>flag</type> <tags> <tag>4</tag> </tags> <steam_id>ACH_WIN_100_GAMES</steam_id> </achievement> <achievement>...</achievement> </achievements>
Each achievement needs an
<achievement>
element. The details of each element inside are:
- 1 This is the numeric id of the achievement. It should be an integer, starting from 1, and go up from there. It needs to be unique.
- <name>name</name> This is the name of the achievement. It is not currently used, but it will show in game some day.
- <type>flag</type> For now, just set this to flag.
- <tags> ... </tags> This is a list of tags you want to track. It should have a number of <tag>id</tag> elements inside, one per tag. The achievement will be rewarded if all these tags are on simultaneously.
- <steam_id>steam id</steam_id> This contains the id of the achievement as defined in Steamworks. This should be entered exactly as it is shown in the portal.
For an example of a full XML file, see the github repo: https://github.com/pkmnfrk/ohrrpgce/blob/steam/testgame/achievements.rpgdir/achievements.xml
3. Insert achievements into your game[edit]
If your game is in .rpgdir format, you can skip this. If your game is in .rpg format, then you need to unlump it. Ensure the game is closed in game.exe and custom.exe, and run the unlump tool:
unlump <mygame>.rpg
This should create mygame.rpgdir.
Next, you need to use xml2reload to compile the xml file. Run the command like this:
xml2reload <path\to\xml> mygame.rpgdir\achievements.reld
Finally, if you unlumped your game and want to relump it, then run this command:
relump <mygame>.rpgdir
4. Testing[edit]
Once you have inserted the achievement data, you can start testing. If everything is working then as soon as you flip on the tags defined in the XML, then it will give you the achievement.
Steam achievements are permanent (by default), so rewarding achievements can be annoying to test. Fortunately, you can ask Game to remove it for you, using a command line parameter:
game --reset_platform_achievements
This will strip all achievements defined in the next loaded game from your Steam profile.
Troubleshooting[edit]
I'm not getting an achievement I was expecting![edit]
First, ensure that Steamworks is loading properly. Do you see notifications when you load game.exe saying "Open the steam overlay", etc? If not, then ensure you have provided the Steamworks binary and set up the steam_appid.txt (see above for details)
If Steamworks is loading, then double check that the expected tags are set. You can do this with the F4 debugging tool in-game. Ensure the IDs of the tags match the ones in the XML, and ensure that if you have more than one that all are set.
Next, make sure you haven't gotten the achivement already. See the Testing section above for details on how to reset Steam achievements
Finally, if all else fails check g_debug.txt. Achievement-related logging is prefixed with "achievement:". If there are any errors, double check that the format of the XML is correct and matches the example provided above.
I have another problem not covered above, or your troubleshooting didn't work[edit]
If all else fails, then please contact me (Mike Caron) on Discord (pkmnfrk#5499, ideally in the #engine_dev channel on the Slime Salad discord) or through the mailing list.