Skip to content

Writing Implementations

API State: Draft

Unfinished

This part of the API is brand-new. It will be changed if there are any bugs, missing features or usability improvements.

It is not recommended relying on this part of the API, it will most likely change.

Draft

Our own usage and testing has shown that this part of the API is complete and seems bug free. However, other plugins may have other use cases which are not covered by our testing. Therefore, please go ahead and use this API part. Let us know if there are missing features or bugs. This API part will be changed if there are more bugs, missing features or usability improvements.

Please use this part of the API and give us feedback!

Stable

Both our own and third party testing showed that this part of the API is complete. Only bugs and major conceptual problems would lead to more changes.

This part of the API should be safe to use. We try to keep it compatible with previous versions if changes are needed.

SummaryπŸ”—

This page covers the creation of new Quest Types (e.g., Event) and Soonβ„’ Features (e.g., Conversation IO) implementations and how they are registered in BetonQuest so that they can be used on the server.

Writing New Quest Type ImplementationsπŸ”—

The following concepts are defined as "Quest Types", as they build the core of BetonQuest.

  • Condition
  • Event
  • Objective
  • Variable

The API is located in the org.betonquest.betonquest.api.quest package. From the list above the Objective is still part of the LegacyAPI. The sub-packages contain the core quest types that are registered in their respective QuestTypeRegistry using the register method.

For an easy event implementation see the Burn Event Package.

Factory PatternπŸ”—

To create Quest Type instances, the Factory Pattern is used. This allows the constructing factory to provide any required objects that the specific implementation needs to function and enables more advanced compositions. For example in Online, Mixed, and Main Thread.

In this context, you register not the actual implementation, but a factory that will create it. Into the factory you inject the dependencies that are required for creating the implementation. The Instruction, providing the "configuration", will be parsed in the factory's parse method, where the implementation is actually constructed. With that the implementation usually does not need the Instruction, allowing much cleaner object orientation implementations.

PlayerlessπŸ”—

To create a quest type that works without any player or profile reference, you can use the Playerless variant, where the resolving does not take a profile parameter (e.g., real-time references).

You simply implement the respective PlayerlessQuestFactory and register the instance.

ProfileπŸ”—

To create a quest type that works with a profile (documentation required) reference, you can use the Profile variant where the resolving takes the player profile as a parameter (e.g., points).

You simply implement the respective PlayerQuestFactory and register the instance.

OnlineProfile / PlayerπŸ”—

To create a quest type that works with a player object reference, you can use the OnlineProfile variant, where the resolving requires an OnlineProfile as a parameter (e.g., inventory access).

You can obtain the Player from the OnlineProfile using the getPlayer() method.

Online<Quest>Adapter serves as a facade for the Profile, allowing the same methods to register and use.

To create the instance to register, simply implement the respective PlayerQuestFactory and wrap the implementation created in the parse method with the Online<Quest>Adapter.

MixedπŸ”—

You can also create a quest type that allows both a Profile and null as arguments using the Nullable<Quest> interface. The primary difference from the Profile variant is the nullability of the parameter.

This is commonly used when the type does not require a profile but uses variables that accept a profile for resolution Typically, the Instruction is parsed in a method that creates the Nullable<Type>, which is then wrapped in the overridden methods with their Nullable<Type>Adapter.

Additionally, you can create two independent types returned by different parse methods by the factory, for example, to utilize the Online<Quest>Adapter for the profile part.

You simply implement the corresponding PlayerQuestFactory and PlayerlessQuestFactory. However, due to Java's Runtime Type Erasure, the register method for this is registerCombined.

Executing on (Bukkit) Main ThreadπŸ”—

BetonQuest attempts to run heavy operations asynchronously to avoid impacting the server's tick rate. To ensure your code runs on the Bukkit main thread (e.g., when interacting with world state), you can wrap it with a PrimaryServerThread<Type> (located in the org.betonquest.betonquest.quest.<type> package). Simply provide the Type to sync and a PrimaryServerThreadData (in the org.betonquest.betonquest.quest package).

LegacyπŸ”—

Currently, you can still register the legacy implementation (from <QuestType>.class).

RegistryπŸ”—

Registering implementations is accomplished through various Registry objects, which store them and make them accessible on the server by different parts of BetonQuest.

The separation is as follows:

  • QuestTypeRegistries, which provide instruction-based object creation:
  • Condition
  • Event
  • Objective
  • Variable
  • FeatureRegistries, which cover more complex and varied creation patterns:
  • ConversationIO
  • Interceptor
  • NotifyIO
  • Schedule

These can be accessed through the getQuestRegistries() and getFeatureRegistries() methods on the plugin.

The QuestItem will be added to the QuestTypeRegistries after its overhaul.

For writing new features, you currently need to reference existing code. See also Legacy creating ConversationIO.