Fzzy Config

Result Providers

The Result Provider system is a framework for retrieving values from a config without directly interacting with that config in-code.

A straightforward example is a loot table with a configurable drop chance for a powerful loot pool. Traditionally, the only way to modify this easily would be to override the loot table with a separate data pack. Result Providers let us define a new loot number provider that draws its value from a config value, allowing the drop chance to be altered in-game from a config GUI.

Result Provider

A result provider is an interface with one job: provide a result based on a passed config scope. The Translation page has a decent overview of what scope is in Fzzy Config's context. It works similarly to a web address, or forge/luckperms permission ids.

ResultProvider<Boolean> myProvider = TODO();
public boolean getMyResult() {
return myProvider.getResult("my_mod.my_config.mySetting");
}

Creating a Provider

Create a simple implementation of a config-backed Result Provider using the ResultApi via ConfigApi.result() or ConfigApiJava.result()

Creation of a simple provider is now added to the above example. This system requires a fallback (false in this case)

ResultProvider<Boolean> myProvider = ConfigApiJava.result().createSimpleResultProvider(false, Boolean.class);
public boolean getMyResult() {
return myProvider.getResult("my_mod.my_config.mySetting");
}

Using the Provider

The above examples show a simple usage example. It is important to note that the scope is dynamic; it can point to any valid boolean-producing setting in any registered config. Including configs of other mods.

This makes result providers a convenient way to interface with another mod without ever having to depend on it in-code. If for example, you are an addon to another mod, interfacing with a config setting this way is a change-robust way of retrieving the value you need. The parent config can add 5000 other settings, change package name, class name, and so on, and the value retrieval will still work. In the case that the scope becomes invalid, fzzy config will fail soft; throwing a log error and using your fallback approach.

//both of these are valid targets for the boolean provider created above
boolean mySetting = false;
ValidatedBoolean mySetting2 = new ValidatedBoolean(false);

Example: Loot Table with dynamic drop chance

Below is the example loot table. It includes a rare drop that is valuable for mod progression, much like a wither skeleton skull.

Currently, the drop is hard-wired to exactly the same random chance as a wither skull.

Using result providers we can make this table dynamically linked with a config.

{
"type": "minecraft:entity",
"pools": [
{
"entries": [
{
"type": "minecraft:item",
"name": "minecraft:redstone"
}
],
"rolls": 1.0
},
{
"conditions": [
{
"chance": 0.025,
"condition": "minecraft:random_chance_with_looting",
"looting_multiplier": 0.01
}
],
"entries": [
{
"type": "minecraft:item",
"name": "my_mod:important_progression_item"
}
],
"rolls": 1.0
}
]
}

Float Provider

To make this table dynamic, we will need a provider of float values to feed into our loot number provider.

ResultProvider<Float> floatProvider = ConfigApi.result().createSimpleResultProvider(0f, Float.class);

Number Provider

Next we wire this provider into a LootNumberProvider. Note the scope param. This is the scope passed in from the loot table definition that we will be providing from our config.

public class ConfigLootNumberProvider implements LootNumberProvider {
private final String scope;
public ConfigLootNumberProvider(String scope) {
this.scope = scope;
}
@Overrride
float nextFloat(LootContext context) {
return floatProvider.getResult(scope);
}
//getType, registration of the type, codec, etc. are assumed
private static final ResultProvider<Float> floatProvider = ConfigApiJava.result().createSimpleResultProvider(0f, Float.class);
}

Json Integration

Let's assume our presumptive type and codec are registered under the identifier my_mod:chance_provider and takes one string scope. The number provider json would look like:

{
"type": "my_mod:chance_provider",
"scope": "my_mod.my_config.progressionDropChance"
}

The Config

Our config, registered under the identifier my_mod:my_config needs the setting(s) we want to refer to.

It might also be a good idea to include the looting multiplier. (And an even better idea to move these into a separate section/object/group for organization, but that's outside the example)

public class MyConfig extends Config {
public MyConfig() {
super(Identifier.of("my_mod", "my_config"));
}
@ValidatedFloat.Restrict(min = 0f, max = 1f)
float progressionDropChance = 0.025f;
@ValidatedFloat.Restrict(min = 0f, max = 1f)
float progressionLootingMultiplier = 0.01f;
}

Table Integration

All the pieces can now be tied together; the new provider condition can be used in loot tables.

This table will now have its chance dynamically updated from the config definition. Updates to the config values will be applied right away, no resource reload needed.

{
"type": "minecraft:entity",
"pools": [
{
"entries": [
{
"type": "minecraft:item",
"name": "minecraft:redstone"
}
],
"rolls": 1.0
},
{
"conditions": [
{
"chance": {
"type": "my_mod:chance_provider",
"scope": "my_mod.my_config.progressionDropChance"
},
"condition": "minecraft:random_chance_with_looting",
"looting_multiplier": {
"type": "my_mod:chance_provider",
"scope": "my_mod.my_config.progressionLootingMultiplier"
}
}
],
"entries": [
{
"type": "minecraft:item",
"name": "my_mod:important_progression_item"
}
],
"rolls": 1.0
}
]
}