-
-
Notifications
You must be signed in to change notification settings - Fork 34
The Generic Modification System
Blueprint includes a package that contains various code files to ease the creation of data-driven modification systems.
Before continuing, we recommend reading the Modifier Systems page first.
Modifiers are the most important part of the Generic Modification System as they represent an object that can modify a specific type of object in a configurable way.
The IModifier interface represents a Modifier. This interface tells how to modify a specific object type using a config through its modify()
method. Additionally, the interface contains serialize()
and deserialize()
methods for serialization and deserialization of its config type.
Simply put, the IModifier
interface is an unconfigured modification procedure.
The ConfiguredModifier class represents a Configured Modifier
.
Instances of the ConfiguredModifier
class contain an IModifier
instance and a config object instance. This class also contains a modify()
method for using the internal modifier with the internal config object to modify a specific type of object.
With this in mind, a JSON structure for a ConfiguredModifier
would look like the following:
{
"type": "some_modifier_type",
"config": (uses structure of the type's config)
}
To start a new Modification System, a ModifierRegistry must get created.
A ModifierRegistry
contains a BiMap
used for looking up IModifier
instances by name and vice-versa. This class is so important because instances of it get used for serializing and deserializing ConfiguredModifier
instances.
A registry class for modifiers that modify a specific type of object may look something like the following:
public final class MyModifiers {
public static final ModifierRegistry<TypeToModify, MySerializationContext, MyDeserializationContext> REGISTRY = new ModifierRegistry<>();
public static final MyModifier MY_MODIFIER_A = REGISTRY.register("my_a", new MyModifier(...));
}
We recommend you leave your ModifierRegistry
instances as public so other mods can add their own modifiers to get used in your systems.
Data-driven modifiers are nothing without a way to target which objects you want to modify. Blueprint has a built-in system for targeting a specific list of named resources, known as the Modifier Target Selector System.
A modifier target selector is represented by the ModifierTargetSelector interface. This interface tells how to select targets using a config through its getTargetNames()
method. Additionally, the interface contains serialize()
and deserialize()
methods for serialization and deserialization of its config type.
Blueprint has a few built-in ModifierTargetSelector
implementations:
Type | Usage | Config |
---|---|---|
choice |
Chooses to use one of two configured selectors based on a condition. | {"first":(configured selector),"second":(configured selector),"condition":(Forge ICondition)} |
empty |
Chooses no targets. | {} |
multi |
Chooses targets using multiple configured selectors. | [(configured selector)...] |
names |
Chooses a list of configured targets. | ["name"...] |
regex |
Chooses a list of targets based on a configured regular expression. | (regular expression) |
A configured modifier target selector is represented by the ConfiguredModifierTargetSelector class.
Instances of the ConfiguredModifierTargetSelector
class contain an ModifierTargetSelector
instance and a config object instance. This class also contains a getTargetNames()
method for using the internal modifier target selector with the internal config object to select targets.
Also, when a ConfiguredModifierTargetSelector
instance gets deserialized, a conditions
field is used to disable the selector if a list of conditions is true.
The JSON structure for a ConfiguredModifierTargetSelector
instance would look like the following:
{
"type": "some_type",
"config": (uses structure of the type's config),
"conditions": [(Forge ICondition)...]
}
Modifiers and target selectors are useful on their own, but combining them allows for even greater possibilities.
The TargetedModifier class represents a list of configured modifiers with conditions, a target selector, and a priority.
The priority
field takes on one of Forge's EventPriority
enum values. These priorities go from HIGHEST
to LOWEST
and are used to prioritize the processing of configured modifiers to assist in resolving compatibility issues between configured modifiers.
TargetedModifier
instances are not stored in memory for re-use but are used for data generators and mapping out prioritized configured modifiers to specific targets.
The JSON structure for a TargetedModifier
instance is as follows:
{
"target": {
"type": "modifier_target_selector_type",
"config": (uses structure of the type's config)
},
"modifiers": [(configured modifier)...]
}
All modification systems use the ModifierTargetSelectorRegistry enum as the registry for ModifierTargetSelector
instances. This means that every registered ModifierTargetSelector
instance can get used in every modifier system that uses the Generic Modification System.
Modders can register their own ModifierTargetSelector
implementations simply by doing ModifierTargetSelectorRegistry.INSTANCE.register(name, selector)
during mod loading.
A Modification Data Manager is simply a name for a system that deserializes and runs targeted modifiers that modify a specific type of object.
The Generic Modification System does not require modders to manage the deserialization of their targeted modifiers in a specific way but does include the ModificationManager class to simplify the creation of managers for simple JSON reloaders.
We all know how annoying it can be to make tons of JSON files for stuff by hand, and fortunately, Blueprint comes with the ModifierDataProvider class to mitigate this for modifiers.
This class is pretty complex like many other parts of the Generic Modification System as it needs to be robust. The ModifierDataProvider
class implements the DataProvider
interface to get used as a data generator.
Like many other DataProvider
implementations, the class contains a DataGenerator
instance, a String
name, and a Gson
instance.
The rest of the fields in the class are unique, so a table is provided for them:
Field | Type | Purpose |
---|---|---|
pathResolver |
BiFunction<Path, ProviderEntry<T, S, D>, Path> |
To resolve the paths for ProviderEntry instances. |
targetKeyName |
String |
Tells what name to use for the targeted modifiers' target key. |
modifierRegistry |
ModifierRegistry |
For serializing the configured modifier instances. |
additionalSerializationGetter |
Function<TargetedModifier<T, S, D>, S> |
To get the additional serialization object for TargetedModifier instances. |
entries |
List<ProviderEntry<T, S, D>> |
To store the list of ProviderEntry instances to serialize. |
Blueprint has a few of its own usages of the ModifierDataProvider
class, which are in LootModifiers#createDataProvider() and AdvancementModifiers#createDataProvider().
The ProviderEntry static nested class represents an entry to get serialized by a ModifierDataProvider
instance. This class contains a TargetedModifier
instance, a 2D array for the conditions for the TargetedModifier
instance, and a ResourceLocation
name for identifying the entry.
ModifierDataProvider
instances use a list of ProviderEntry
instances to serialize TargetedModifier
instances with conditions.