UnlockableAPI - risk-of-thunder/R2API GitHub Wiki
This submodule allows you to add custom achievements and unlockables to your mod.
Important types and functions
UnlockableAPI.AddUnlockable
public static void AddUnlockable<TUnlockable>( bool serverTracked );
where TUnlockable : BaseAchievement, IModdedUnlockableDataProvider, new()
This method adds the achievement described by TUnlockable
.
In nearly all cases, TUnlockable
should be a type inheriting ModdedUnlockable
. This method works off the interfaces solely for flexibility.
serverTracked
indicates if the achievement unlock conditions are tracked by the server or the client.
Throws when
- The submodule is not loaded
- AchievementManager or UnlockableCatalog have already initialized (they initialize after plugin
Awake()
andOnEnable()
but beforeStart()
) - The supplied reward identifier is already in use by another mod or the base game.
- any implementation code in
TUnlockable
that is called here throws.
Usage Example
// Usings and standard attributes are omitted for simplicity
[R2APISubmoduleDependency( nameof(UnlockableAPI) )]
public class MyExampleMod : BaseUnityPlugin
{
// Add the unlockable in Awake (or OnEnable) or it will be too late
protected void Awake()
{
// This call adds the unlockable type
UnlockableAPI.AddUnlockable<ExampleUnlockable>( true );
}
}
// The type that is used to implement the functionality of the achievement and provide the relevant metadata
// VanillaSpriteProvider can be swapped for anything that implements IAchievementSpriteProvider.
// There is an additional provider for use with ResourcesAPI called CustomSpriteProvider.
public class ExampleUnlockable : ModdedUnlockable<VanillaSpriteProvider>
{
// All of the strings defined here must be unique in order to interop with both the base game and other mods.
// For that reason, you should always use something of the format "Author_Mod_Achievement_..."
// Note that changing these values is a breaking change for your mod. All users will have to redo the challenge if the id changes.
// Also, note that in most cases it is a good idea for you to add a config option to reset unlockable progress for users.
// Not only does this make testing much easier, but it also allows users to gain some potential replayability.
// The name used to identify the achievement.
public override String AchievementIdentifier { get; } = "MYNAME_MYEXAMPLEMOD_EXAMPLEUNLOCKABLE_ACHIEVEMENT_ID";
// The key used to identify things that are unlocked by this.
public override String UnlockableIdentifier { get; } = "MYNAME_MYEXAMPLEMOD_EXAMPLEUNLOCKABLE_REWARD_ID";
// The key of a prereq for unlocking this. Use "" for none.
public override String PrerequisiteUnlockableIdentifier { get; } = "MYNAME_MYEXAMPLEMOD_EXAMPLEUNLOCKABLE_PREREQ_ID";
// Language token for the achievement name.
public override String AchievementNameToken { get; } = "MYNAME_MYEXAMPLEMOD_EXAMPLEUNLOCKABLE_ACHIEVEMENT_NAME";
// Language token for the achievement description.
public override String AchievementDescToken { get; } = "MYNAME_MYEXAMPLEMOD_EXAMPLEUNLOCKABLE_ACHIEVEMENT_DESC";
// Language token for the unlockable name.
public override String UnlockableNameToken { get; } = "MYNAME_MYEXAMPLEMOD_EXAMPLEUNLOCKABLE_UNLOCKABLE_NAME";
// The sprite provider.
protected override VanillaSpriteProvider SpriteProvider { get; } = new VanillaSpriteProvider( "VANILLA PATH" );
// All of the functionality and behaviour of your achievement is implemented through overriding base methods
// A function we are registering to an event.
public void CheckIfDeathIsABeetle( DamageReport report )
{
// Null checks, because good people don't break the game by throwing exceptions in their hooks/events.
if( report is null ) return;
if( report.victimBody is null ) return;
// Not a very good way to check, but works for demonstration. The proper approach would involve getting the bodyindex and checking that.
if( report.victiomBody.name.Contains( "BeetleBody" ) )
{
// Grants the achievement.
base.Grant();
}
}
// This is called in order to set up the checks for the achievement.
public override void OnInstall()
{
// In general, always should call the base, unless you know what you are doing.
base.OnInstall();
// Register our function to onCharacterDeathGlobal to check if the thing that died was a beetle.
// You are not limited to vanilla events. You can even apply hooks here if you need to.
GlobalEventManager.onCharacterDeathGlobal += this.CheckIfDeathIsABeetle;
}
// This is called when the achievement is granted in order to clean up the checks.
// You should be removing events/hooks that you register in OnInstall here.
public override void OnUninstall()
{
base.OnUninstall();
GlobalEventManager.onCharacterDeathGlobal -= this.CheckIfDeathISABeetle;
}
}