API State: Unfinished
Todo
This part of the API is only planned.Do not use anything of this part of the API yet. It will change in the future.
Unfinished
This part of the API is brand-new. It will be changed if there are any bugs, missing features or usability improvements. This part of the API might receive breaking changes within a minor version.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. We will try to keep it compatible with previous versions if changes are needed. This part of the API won't receive breaking changes within a minor version.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 will only change over at least one major version and will likely carry deprecation as long as possible.This part of the API should be safe to use. We try to keep it compatible with previous versions if changes are needed.
Overviewπ
This page intends to give an elaborate overall introduction to the API and all its basic concepts.
What this page covers
What this page does not cover
- What an API is
- How an implementation example might look like
- How to obtain the API
- How to integrate the API into your project
API Structureπ
The API is structured to be intuitive to navigate and grants you access to all its features with short stream-like paths
while retaining the ability to inject parts into your own classes to comply with the Law of Demeter.
The tree-like capsulation of features ensures a narrowing scope as you move down the path.
Chart
---
title: Obtain BetonQuestApi
---
flowchart LR
Service("BetonQuestApiService")
API@{ shape: procs, label: "BetonQuestApi"}
Service target@--> API
target@{ animate: true }
---
title: Access BetonQuestApi Features
---
flowchart LR
API@{ shape: procs, label: "BetonQuestApi"}
subgraph two [Features]
direction LR
profiles("profiles()")
loggerFactory("loggerFactory()")
actions("actions()")
more@{ shape: procs, label: "and more..."}
end
API --> profiles
API --> loggerFactory
API target@--> actions
API --> more
target@{ animate: true }
---
title: Access Feature Details
---
flowchart LR
actions("actions()")
subgraph three [Feature Details]
direction LR
manager("manager()")
registry("registry()")
end
actions target@--> manager
actions --> registry
target@{ animate: true }
---
title: Overview
---
flowchart LR
Service("BetonQuestApiService")
subgraph one [API]
direction LR
API@{ shape: procs, label: "BetonQuestApi"}
subgraph two [Features]
direction LR
profiles("profiles()")
loggerFactory("loggerFactory()")
actions("actions()")
more@{ shape: procs, label: "and more..."}
subgraph three [Feature Details]
direction LR
manager("manager()")
registry("registry()")
end
actions target3@--> manager
actions --> registry
end
API --> profiles
API --> loggerFactory
API target2@--> actions
API --> more
end
Service target1@--> API
target1@{ animate: true }
target2@{ animate: true }
target3@{ animate: true }
Explanation
On the previous page, we learned how to obtain the API.
The highlighted step is equivalent to betonQuestApiService.api(yourPlugin)
Once the API is obtained, we can access the features of the API.
Among these features are the profiles(), loggerFactory(), actions() and many more.
They are partially discussed in more detail in the concepts section below.
Some features have their own pages, which are linked in the sidebar by their name.
The highlighted step is equivalent to betonQuestApi.actions()
Some features have their own sub-features that essentially split up the feature into smaller parts.
Most factory-type features have a manager() and a registry() sub-feature.
Learn more about how BetonQuest uses factories in the concepts section below.
The highlighted step is equivalent to actions.manager()
The API is structured in a way that it is easy to navigate and therefore allowing you to access all features
with short paths while retaining the ability to inject parts into your own classes to comply with the Law of
Demeter.
The highlighted steps are equivalent to betonQuestApiService.api(yourPlugin).actions().manager()
API Conceptsπ
The following sections will give you a more in-depth overview of the API's concepts and ideas. This section is not necessary to use the API, but it will help you understand the API's structure and how it works.
Identifiersπ
An identifier refers to a specific element in a quest package, consisting of the path to the containing package and the element's name.
Unlike identifiers used in packages,
API identifiers always contain the full absolute package path (no relative context exists) and are always type-specific.
An ActionIdentifier may have the same string representation as an ObjectiveIdentifier, but they refer to different types and cannot be used interchangeably.
This type safety prevents accidentally passing an action identifier where a condition identifier is expected, providing compile-time validation.
Identifiers serve as the primary mechanism for locating and executing quest components at runtime. The corresponding manager performs a lookup to retrieve the instance created during quest package loading. This relationship maintains consistency across server restarts and quest package reloads, as the same identifier always resolves to functionally identical instances when recreated from the same instruction.
Factories, Registries, Managersπ
The factory design pattern is used in the API to create objects without having to know their concrete classes. In BetonQuest, this concept is further enhanced by instructions for configurability and to ensure that identical objects can be recreated consistently. Each instruction defines how to create a specific instance, and the factory uses this instruction to produce the actual object. This allows BetonQuest to reload quest packages and recreate identical object instances from the same instructions, maintaining consistency across server restarts.
The following explanation uses object as a placeholder, since the concepts apply to actions, conditions, objectives, and other
factory-based features throughout the API.
To create a new object, four components are required in the process:
- Implementation β The concrete
objectclass that performs the actual game logic - Factory β Creates instances of the
objectfrom instructions and validates its parameters - Registry β Stores and looks up
objectfactories by their type key (e.g.,block,give,notify) - Manager β Maintains created
objectinstances, accessible by their identifier for execution during server runtime
Implementation & Factoryπ
During BetonQuest's loading phase, an object instance is created for each instruction found in the quest package.
Each factory is identified by the instruction's first element (its type key) and is responsible for creating an object instance from that instruction.
The factory performs several critical tasks:
- Parsing: Extracts and validates all parameters from the instruction string
- Validation: Ensures all required parameters are present and correctly formatted
- Instantiation: Creates the concrete
objectimplementation with the parsed configuration - Error handling: Provides clear error messages if the instruction is malformed (mostly abstracting away by the instruction implementation)
The instruction's identifier is associated with the created object instance, establishing a bidirectional relationship.
When an object needs to be invoked β whether by an action, an objective, or any other component β the identifier is used to
locate and execute the correct object instance from the manager.
This separation between factory (creation logic) and implementation (execution logic) allows for clean code organization
and makes it easier to add new object types without modifying existing code.
---
title: Instancing
---
flowchart LR
Instruction -. associated by Identifier .- Instance
Instruction["Instruction #1"] target1@-- parse(#1) --> Factory
Factory target3@-- #1 --> Instance["Instance #1"]
Instruction2["Instruction #2"] target2@-- parse(#2) --> Factory
Factory target4@-- #2--> Instance2["Instance #2"]
Instruction2 -. associated by Identifier .- Instance2
target1@{ animate: true, animation: slow, curve: linear }
target2@{ animate: true, animation: slow, curve: linear }
target3@{ animate: true, animation: slow, curve: linear }
target4@{ animate: true, animation: slow, curve: linear }
Registryπ
A registry functions as a structured map that maintains associations between instruction type keys and their corresponding factories.
Each registry is type-specific: the object registry maintains all available object factories, the condition registry maintains all condition factories, etc.
The registry serves as the central lookup mechanism for factories:
- Registration: Plugins register their custom
objectfactories with unique type keys during initialization and pre-package loading - Lookup: During quest package loading, BetonQuest queries the registry using the instruction's type key to find the appropriate factory
- Validation: The registry can validate that a requested factory exists before attempting to create instances
To introduce a new object type, its factory must be registered with the object registry using a unique type key.
During BetonQuest's loading phase, the system queries the registry to locate the appropriate factory for each instruction type,
instantiates the corresponding objects, and stores these instances in the manager for later retrieval and execution.
The registry pattern ensures loose coupling between the core system and individual object implementations, allowing BetonQuest
to support both built-in and plugin-provided objects through the same unified interface.
---
title: Registration and Lookup
---
flowchart LR
Factory["Factory"] target1@-- register(key) --> Registry
Registry target2@-- lookup(key) --> Factory2["Factory"]
target1@{ animate: true, animation: slow }
target2@{ animate: true, animation: slow }
Managerπ
A manager is essentially a map of known instances for each identifier grouped by a certain type of instruction. All active and loaded instances are stored in the manager, providing runtime access to them.
The manager's responsibilities include:
- Storage: Maintains a collection of all created instances
- Retrieval: Provides fast lookup of instances by identifier
- Lifecycle: Manages the lifecycle of instances, handling cleanup when quest packages are reloaded
When a quest package is loaded, the manager is populated with instances created by factories. When a quest package is unloaded or reloaded, the manager removes all associated instances to prevent memory leaks and ensure the new versions are used.
---
title: Instance Storage
---
flowchart LR
Registry target1@-- lookup --> Factory
subgraph one [Instancing]
direction LR
Factory -.-> Junction
Junction target2@-- parse --> Instance
Instruction -.-> Junction
end
Instance target3@-- store --> Manager
target1@{ animate: true, animation: slow }
target2@{ animate: true, animation: slow }
target3@{ animate: true, animation: slow }
Instruction@{ shape: hex }
Junction@{ shape: f-circ }
Hence, to run, for example, an action using its identifier, the manager will be able to find the correct action instance and execute it.
---
title: Instance Access
---
flowchart LR
Start target1@-- run(identifier) --> Manager
subgraph one [Hidden]
direction LR
Manager target2@-- lookup(identifier).run() --> Instance
end
target1@{ animate: true, animation: slow }
target2@{ animate: true, animation: slow }
Start@{ shape: sm-circ }