-
Notifications
You must be signed in to change notification settings - Fork 275
Migrating to the new settings system
This is a guide for extension developers to easily migrate from the old settings system to the new one. If you're just a player or server owner, you shouldn't need to care about this.
No, you have a bit of time, don't worry. The old system is likely to stay for the rest of the 1.19 release cycle, you'll just get warnings about it in the logs. Note that the old system is going to be removed though, so don't hold upgrading for too long!
Rule descriptions and similar are now picked up via the translation system! That means that instead of defining the description and extra info in the annotation, you'll have to put them in a json file for (at least) english, and provide those translations to Carpet via your CarpetExtension
. Here's how you do it:
You'll need a file called en_us.json
inside your resources/[modid]/lang
folder. That file will contain all of the translatable strings for your mod's rules and own categories.
Here's a small example of the contents it should have. If you're not adding your rules to CarpetServer.settingsManager
, replace "carpet" with your manager's identifier.
{
// Rule description
// Follows the format "carpet.rule.<rule name>.desc"
"carpet.rule.cleanLogs.desc": "Removes abnoxious messages from the logs",
// Rule extra info
// Follows the format "carpet.rule.<rule name>.extra.<index>"
// If present, must start at index 0. It's not required
"carpet.rule.cleanLogs.extra.0": "Doesn't display 'Maximum sound pool size 247 reached'",
"carpet.rule.cleanLogs.extra.1": "which is normal with decent farms and contraptions",
// Alternative rule names
// This is to provide translated names in other languages, displayed next to the actual
// rule name in those. It's not required.
"carpet.rule.cleanLogs.name" : "cleanLogs",
// Custom Categories
// Follows the format "carpet.category.<category id>"
// You don't have to add strings for those in RuleCategory
"carpet.category.bugfix": "bugfix"
}
You have to do that for every rule you have. Too many rules? Don't worry, while updating Carpet Extra I made a small piece
of code to generate a language file from your existing rules, with pretty comments, formatting and spacing. All you need is
to have updated your dependency on Carpet, and then place the following code at some point after you've registered your rules
to the SettingsManager
. Change the condition in the filter to something that only matches your rules, and start the game.
Note that some special cases may not be handled correctly and it'll add a comma at the end, but it should work for at least
most of your rules.
Path languagePath = Path.of("en_us.json");
try (var out = new PrintStream(Files.newOutputStream(languagePath), true, StandardCharsets.UTF_8)) {
out.print('{');
CarpetServer.settingsManager.getRules().stream().filter(r -> r.categories().contains(CarpetExtraSettings.EXTRA))
.sorted().forEach(pr -> {
out.println();
out.println(" // " + pr.name);
if (!pr.description.isBlank()) {
out.print("""
"%s": "%s",
""".formatted(TranslationKeys.RULE_DESC_PATTERN.formatted("carpet", pr.name), pr.description.replace("\"", "\\\"")));
}
if (!pr.extraInfo.isEmpty()) {
out.println();
int i = 0;
for (String info : pr.extraInfo) {
out.print("""
"%s": "%s",
""".formatted(TranslationKeys.RULE_EXTRA_PREFIX_PATTERN.formatted("carpet", pr.name) + i, info));
i++;
}
}
});
out.println('}');
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Once you have the file you'll need to provide its contents to the translations system. Currently you do that by returning
a map in CarpetExtension::canHasTranslations(String lang)
. An example implementation for that method to read
your newly created language file is as simple as the following (feel free to use it!):
public Map<String, String> canHasTranslations(String lang) {
InputStream langFile = ExtensionClass.class.getClassLoader().getResourceAsStream("assets/yourModId/lang/%s.json".formatted(lang));
if (langFile == null) {
// we don't have that language
return Collections.emptyMap();
}
String jsonData;
try {
jsonData = IOUtils.toString(langFile, StandardCharsets.UTF_8);
} catch (IOException e) {
return Collections.emptyMap();
}
Gson gson = new GsonBuilder().setLenient().create(); // lenient allows for comments
return gson.fromJson(jsonData, new TypeToken<Map<String, String>>() {}.getType());
}
And that should be it! You now have a language file that's getting read and added to the Carpet translation system!
So a lot of classes changed, like a lot. But most of them were just changing package! All kept API that was in carpet.settings
is now in
carpet.api.settings
. There's documentation about the replacements for the APIs that will tell you if it's a simple package change or if you have
to change something else. Feel free to read it, but in this guide we'll go through what affects most extensions.
Well, first of all, change all your imports about settings to the new package, and that should already get rid of many deprecation warnings.
However, now you are likely to see a few errors arise. This is because, as we just saw, messages have moved from the annotation to the language file, so all of them will error. Assuming you migrated them to the language file, all you need to do is to just remove the strings from the annotations.
You'll have already noticed that your Validators have errors too. Their fix is very simple in most cases, you have to change the ParsedRule
parameters
to be CarpetRule
, and that should fix them. You may need to adjust some of your validator code to use the new CarpetRule
methods instead of
direct field accessing to ParsedRule
, but if you open ParsedRule
you'll see all the replacements are documented.
Standard validators have also been moved. Now, instead of being mixed within the Validator
class, they are in Validators
. You should be able
to find any standard validator you used in there.
Condition
is now Rule.Condition
, and the method name has changed to shouldRegister
.
There's also been a few renames in SettingsManager
, mostly because of the need to return the new CarpetRule
instead of ParsedRule
in most
methods. The new methods that replace the old will now refer to CarpetRule
instead of just rule or ParsedRule
(like getCarpetRule
instead of
just getRule
), given ParsedRule may no longer be the only implementation. See the "Why this change?" section for more info about that.
Rule observers are now registered via register[Global]RuleObserver
instead of add
, given the old method references log4j classes directly.
And, if you're one of the few extensions that add your rules to your own SettingsManager
instance, there's a few things you have to know: You should
now extend carpet.api.settings.SettingsManager
instead of the old type directly, though keep in mind that doing that will make you
not be able to use the legacy paths inside it. Mostly because those are only present in the legacy class that extends it. You'll also need to add translation keys for the categories you use in the manager, given those keys are per-manager now, and Carpet only provides for the default manager (we can't know all the managers beforehand).
There's multiple motivations for the change. One of them being that the settings system is an API, even if it's not really mentioned. Carpet keeps it
stable so extensions can use it to register their rules, and it's nice to make it clear that it's an API by having it in an api
package. But a package
rename is not enough to require all extensions to have to change.
Another reason is that it was very fragile and needed some refactoring. It has been in Carpet since before fabric was a thing, and has since been working non-stop. However, there were some stuff that was limiting in it, with some legacy systems that weren't necessary, and very strange contracts. While it hasn't affected most extensions, subtle changes have broken binary (or runtime) compatibility with extensions in what seemed harmless.
The last reason is that it wasn't really extensible. As an extension you just had this block that's defined in code, without any specific contract as to what will the method or field be doing, that you can't alter, and also you can only add rules from scanning fields in a class.
There's also the thing that the system wasn't really made for translations. There's a translation system in Carpet, but translations in Carpet aren't really that good. It's difficult to make a translation system when all messages are hardcoded directly in places like the annotations, and it just leads to code and data duplication, and to a bit of a mess. Work an upgrade to that system has started recently, and it's looking better, and one thing for sure was that rule info was going to be moved to the translation system. This makes it a perfect opportunity to apply the bulk of the changes and not just deprecate parts now, a few others in a while and so on.
This refactor resolves all three of them while keeping full binary compatibility with the old system (for now). The first one's solution is obvious, and
the second and third are done via the introduction of the CarpetRule
interface.
CarpetRule
is the replacement for ParsedRule
in this API, and instead of being a class you can only see the code for, it's a completely defined
interface, that tells you what is a CarpetRule, and what you can do with it. There's no exposed direct fields or methods that don't add anything to
what the class actually is, there's just that basic contract. Those convenience methods, such as resetToDefault
have been moved to a satellite
helper, RuleHelper
, that can deal with the common rule operations that don't need a specific implementation in the rule to work.
That means, there's now a clear separation between contract and implementation. The implementation will always follow the contract, but you are not
required to see exactly how and decipher those details to work. The current implementation, ParsedRule
, for example, is going to be encapsulated
out of the public world, given at some point Carpet may implement some of the rules with a different class! Or you, as an extension!
This system adds the interface, and it's not sealed. You are able to create your own implementation of CarpetRule
and add it to your
SettingsManager
of choice, and it'll be managed as any other implementation, but you have full control over what happens under-the-hood.
SettingsManager has a new method to add your custom CarpetRule
s, addCarpetRule
. Note though, that method will check for duplicates!
If you want to try, check out CarpetRule
and see the requirements you need to implement, and get possibilities that aren't available with current
rules, such as interactive extra info menus, fully custom calls and conditions when changing, or a dynamic list of suggestions!