Refactor config system and introduce ConfigRegistry.

Moved configuration setup to a dedicated ConfigRegistry for better organization and modularity. Simplified config management and added support for multiple namespaces/configurations.

Also added hardcoded option for folder or file configs
This commit is contained in:
Chris Toph 2025-02-22 23:23:03 -05:00
parent 7549757ce3
commit 9136660de7
7 changed files with 395 additions and 377 deletions

View file

@ -1,31 +1,32 @@
package cc.toph.simplycompat; package cc.toph.simplycompat;
import cc.toph.simplycompat.config.Config; import cc.toph.simplycompat.config.Config;
import cc.toph.simplycompat.registry.ConfigRegistry;
import cc.toph.simplycompat.registry.ItemsRegistry; import cc.toph.simplycompat.registry.ItemsRegistry;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
public final class SimplyCompat { public final class SimplyCompat {
public static final String MOD_ID = "simplycompat"; public static final String MOD_ID = "simplycompat";
// Unused TAB, for now added so SS's Tab registry by SSSwordItem class // Unused TAB, for now added so SS's Tab registry by SSSwordItem class
// public static final DeferredRegister<CreativeModeTab> TABS = DeferredRegister.create( // public static final DeferredRegister<CreativeModeTab> TABS = DeferredRegister.create(
// MOD_ID, // MOD_ID,
// Registries.CREATIVE_MODE_TAB // Registries.CREATIVE_MODE_TAB
// ); // );
// public static final RegistrySupplier<CreativeModeTab> SIMPLYCOMPAT = TABS.register(MOD_ID, () -> // public static final RegistrySupplier<CreativeModeTab> SIMPLYCOMPAT = TABS.register(MOD_ID, () ->
// CreativeTabRegistry.create( // CreativeTabRegistry.create(
// Component.translatable("creativeTab.simplycompat.simplycompat"), // Component.translatable("creativeTab.simplycompat.simplycompat"),
// () -> new ItemStack(Items.AMETHYST_SHARD) // () -> new ItemStack(Items.AMETHYST_SHARD)
// ) // )
// ); // );
public static final Logger LOGGER = LogManager.getLogger(MOD_ID); public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
public static void init() { public static void init() {
// TABS.register(); // TABS.register();
Config.registerConfigs(); ConfigRegistry.registerConfigs();
ItemsRegistry.registerAllSwords(); ItemsRegistry.registerAllSwords();
} }
} }

View file

@ -3,73 +3,46 @@ package cc.toph.simplycompat.config;
import cc.toph.simplycompat.SimplyCompat; import cc.toph.simplycompat.SimplyCompat;
public class Config { public class Config {
private SimpleConfig config;
private final ConfigProvider provider;
// public static final String CONFIG_NAME = SimplyCompat.MOD_ID + SimpleConfig.FILE_EXTENSION; public Config(ConfigProvider provider) {
public static SimpleConfig CONFIG; this.provider = provider;
private static final ConfigProvider PROVIDER = new ConfigProvider(); config = SimpleConfig.of(provider.getNamespace()).provider(provider).request();
}
public void register() {
config.update();
}
// TODO: Move some of this stuff into ConfigRegistry // Overloaded registerProp for float
public static final class WeaponAttributes { public float registerProp(String key, float defaultValue, String comment) {
public static float COPPER; provider.addKeyValuePair(key, defaultValue, comment);
public static float STEEL; // Cast needed because getOrDefault for numbers returns double
public static float TEST; return (float) config.getOrDefault(key, defaultValue);
} }
// Overloaded registerProp for int
public int registerProp(String key, int defaultValue, String comment) {
provider.addKeyValuePair(key, defaultValue, comment);
return config.getOrDefault(key, defaultValue);
}
public static void registerConfigs() { // Overloaded registerProp for double
// Load config 'simplycompat.config', if it isn't present create one public double registerProp(String key, double defaultValue, String comment) {
// Temp Default Empty Provider to load config provider.addKeyValuePair(key, defaultValue, comment);
CONFIG = SimpleConfig.of(SimplyCompat.MOD_ID).provider(PROVIDER).request(); return config.getOrDefault(key, defaultValue);
initializeConfigs(); }
CONFIG.update();
} // Overloaded registerProp for String
public String registerProp(String key, String defaultValue, String comment) {
provider.addKeyValuePair(key, defaultValue, comment);
return config.getOrDefault(key, defaultValue);
}
// Overloaded registerConfig for float // Overloaded registerProp for boolean
private static float registerConfig(String key, float defaultValue, String comment) { public boolean registerProp(String key, boolean defaultValue, String comment) {
PROVIDER.addKeyValuePair(key, defaultValue, comment); provider.addKeyValuePair(key, defaultValue, comment);
// Cast needed because getOrDefault for numbers returns double return config.getOrDefault(key, defaultValue);
return (float) CONFIG.getOrDefault(key, defaultValue); }
}
// Overloaded registerConfig for int
private static int registerConfig(String key, int defaultValue, String comment) {
PROVIDER.addKeyValuePair(key, defaultValue, comment);
return CONFIG.getOrDefault(key, defaultValue);
}
// Overloaded registerConfig for double
private static double registerConfig(String key, double defaultValue, String comment) {
PROVIDER.addKeyValuePair(key, defaultValue, comment);
return CONFIG.getOrDefault(key, defaultValue);
}
// Overloaded registerConfig for String
private static String registerConfig(String key, String defaultValue, String comment) {
PROVIDER.addKeyValuePair(key, defaultValue, comment);
return CONFIG.getOrDefault(key, defaultValue);
}
// Overloaded registerConfig for boolean
private static boolean registerConfig(String key, boolean defaultValue, String comment) {
PROVIDER.addKeyValuePair(key, defaultValue, comment);
return CONFIG.getOrDefault(key, defaultValue);
}
// Initialize config fields in one step
private static void initializeConfigs() {
WeaponAttributes.COPPER = registerConfig(
"copper_damageModifier",
3.0f,
"Copper Damage Modifier"
);
WeaponAttributes.STEEL = registerConfig(
"steel_damageModifier",
5.0f,
"Steel Damage Modifier"
);
}
} }

View file

@ -5,34 +5,40 @@ import cc.toph.simplycompat.SimplyCompatExpectPlatform;
import java.util.HashMap; import java.util.HashMap;
public class ConfigProvider implements SimpleConfig.DefaultConfig { public class ConfigProvider implements SimpleConfig.DefaultConfig {
// Key: config key (e.g. "copper_damageModifier") private final String namespace;
// Value: Pair(value, comment) private final HashMap<String, String> configMap = new HashMap<>();
private final HashMap<String, String> configMap = new HashMap<>(); private String contents = String.format("""
private String contents = String.format(""" ## Simply Compat Config
## Simply Compat Config ## Version: %s
## Version: %s
""", SimplyCompatExpectPlatform.getVersion());
""", SimplyCompatExpectPlatform.getVersion());
public void addKeyValuePair(String key, Object value, String comment) { public ConfigProvider(String namespace) {
String stringValue = String.valueOf(value); // safely handle `null` too this.namespace = namespace;
configMap.put(key, stringValue); }
contents += String.format("""
# %s
%s = %s
""", comment, key, value);
}
@Override public void addKeyValuePair(String key, Object value, String comment) {
public HashMap<String, String> getMap() { String stringValue = String.valueOf(value); // safely handle `null` too
return configMap; configMap.put(key, stringValue);
} contents += String.format("""
# %s
%s = %s
""", comment, key, value);
}
@Override @Override
public String get(String namespace) { public String get(String namespace) {
return contents; return contents;
} }
@Override
public HashMap<String, String> getMap() {
return configMap;
}
public String getNamespace() {
return namespace;
}
} }

View file

@ -21,6 +21,7 @@ package cc.toph.simplycompat.config;
* THE SOFTWARE. * THE SOFTWARE.
*/ */
import cc.toph.simplycompat.SimplyCompat;
import cc.toph.simplycompat.SimplyCompatExpectPlatform; import cc.toph.simplycompat.SimplyCompatExpectPlatform;
import java.io.File; import java.io.File;
@ -37,255 +38,255 @@ import static cc.toph.simplycompat.SimplyCompat.LOGGER;
public class SimpleConfig { public class SimpleConfig {
public static final String FILE_EXTENSION = ".config"; public static final String FILE_EXTENSION = ".config";
private final HashMap<String, String> config = new HashMap<>(); private final HashMap<String, String> config = new HashMap<>();
private final ConfigRequest request; private final ConfigRequest request;
private boolean broken = false; private boolean broken = false;
private SimpleConfig(ConfigRequest request) { private SimpleConfig(ConfigRequest request) {
this.request = request; this.request = request;
String identifier = "Config '" + request.filename + "'"; String identifier = "Config '" + request.filename + "'";
if (!request.file.exists()) { if (!request.file.exists()) {
LOGGER.info(identifier + " is missing, generating default one..."); LOGGER.info("{} is missing, generating default one...", identifier);
try { try {
createConfig(); createConfig();
} catch (IOException e) { } catch (IOException e) {
LOGGER.error(identifier + " failed to generate!"); LOGGER.error("{} failed to generate!", identifier);
LOGGER.trace(e); LOGGER.trace(e);
broken = true; broken = true;
} }
} }
if (!broken) { if (!broken) {
try { try {
loadConfig(); loadConfig();
} catch (Exception e) { } catch (Exception e) {
LOGGER.error(identifier + " failed to load!"); LOGGER.error("{} failed to load!", identifier);
LOGGER.trace(e); LOGGER.trace(e);
broken = true; broken = true;
} }
} }
}
/**
* Creates new config request object, ideally `namespace`
* should be the name of the mod id of the requesting mod
*
* @param filename - name of the config file
* @return new config request object
*/
public static ConfigRequest of(String filename) {
// File
// Path path = SimplyCompatExpectPlatform.getConfigDirectory().resolve(filename + FILE_EXTENSION);
// Folder
Path path = SimplyCompatExpectPlatform.getConfigDirectory().resolve(String.format("%s/%s%s", SimplyCompat.MOD_ID, filename, FILE_EXTENSION));
return new ConfigRequest(path.toFile(), filename);
}
public void update() {
// If no provider or if we had an error, do nothing
if (request.provider == null || broken) return;
boolean changed = false;
// For each provider key, see if our config HashMap is missing it
for (var entry : request.getConfigMap().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// If it's missing from 'config', add it
if (!config.containsKey(key)) {
config.put(key, value);
changed = true;
}
}
// If new keys were added, regen the file with createConfig()
if (changed) {
try {
delete();
createConfig();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void createConfig() throws IOException {
// try creating missing files
request.file.getParentFile().mkdirs();
Files.createFile(request.file.toPath());
// write default config data
PrintWriter writer = new PrintWriter(request.file, StandardCharsets.UTF_8);
writer.write(request.getConfig());
writer.close();
}
private void loadConfig() throws IOException {
Scanner reader = new Scanner(request.file);
for (int line = 1; reader.hasNextLine(); line++) {
parseConfigEntry(reader.nextLine(), line);
}
}
private void parseConfigEntry(String entry, int line) {
if (!entry.isEmpty() && !entry.startsWith("#")) {
String[] parts = entry.split("=", 2);
if (parts.length == 2) {
config.put(parts[0].trim(), parts[1].trim());
} else {
throw new RuntimeException("Syntax error in config file on line " + line + "!");
}
}
}
/**
* Queries a value from config, returns `null` if the
* key does not exist.
*
* @return value corresponding to the given key
* @see SimpleConfig#getOrDefault
*/
@Deprecated
public String get(String key) {
return config.get(key);
}
/**
* Returns string value from config corresponding to the given
* key, or the default string if the key is missing.
*
* @return value corresponding to the given key, or the default value
*/
public String getOrDefault(String key, String def) {
String val = get(key);
return val == null ? def : val;
}
/**
* Returns integer value from config corresponding to the given
* key, or the default integer if the key is missing or invalid.
*
* @return value corresponding to the given key, or the default value
*/
public int getOrDefault(String key, int def) {
try {
return Integer.parseInt(get(key));
} catch (Exception e) {
return def;
}
}
/**
* Returns boolean value from config corresponding to the given
* key, or the default boolean if the key is missing.
*
* @return value corresponding to the given key, or the default value
*/
public boolean getOrDefault(String key, boolean def) {
String val = get(key);
if (val != null) {
return val.equalsIgnoreCase("true");
}
return def;
}
/**
* Returns double value from config corresponding to the given
* key, or the default string if the key is missing or invalid.
*
* @return value corresponding to the given key, or the default value
*/
public double getOrDefault(String key, double def) {
try {
return Double.parseDouble(get(key));
} catch (Exception e) {
return def;
}
}
/**
* If any error occurred during loading or reading from the config
* a 'broken' flag is set, indicating that the config's state
* is undefined and should be discarded using `delete()`
*
* @return the 'broken' flag of the configuration
*/
public boolean isBroken() {
return broken;
}
/**
* Deletes the config file from the filesystem
*/
public boolean delete() {
LOGGER.warn("Config '{}' will rise from the ashes! Please restart game.", request.filename);
return request.file.delete();
}
public interface DefaultConfig {
static String empty(String namespace) {
return "";
}
String get(String namespace);
default Map<String, String> getMap() {
return new HashMap<>();
}
}
public static class ConfigRequest {
private final File file;
private final String filename;
private DefaultConfig provider;
private ConfigRequest(File file, String filename) {
this.file = file;
this.filename = filename;
this.provider = DefaultConfig::empty;
} }
/** /**
* Creates new config request object, ideally `namespace` * Sets the default config provider, used to generate the
* should be the name of the mod id of the requesting mod * config if it's missing.
* *
* @param filename - name of the config file * @param provider default config provider
* @return new config request object * @return current config request object
* @see DefaultConfig
*/ */
public static ConfigRequest of(String filename) { public ConfigRequest provider(DefaultConfig provider) {
// Small edit for use in common, super cool! :D this.provider = provider;
Path path = SimplyCompatExpectPlatform.getConfigDirectory().resolve(filename + FILE_EXTENSION); return this;
return new ConfigRequest(path.toFile(), filename);
}
public void update() {
// If no provider or if we had an error, do nothing
if (request.provider == null || broken) return;
boolean changed = false;
// For each provider key, see if our config HashMap is missing it
for (var entry : request.getConfigMap().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// If it's missing from 'config', add it
if (!config.containsKey(key)) {
config.put(key, value);
changed = true;
}
}
// If new keys were added, regen the file with createConfig()
if (changed) {
try {
delete();
createConfig();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void createConfig() throws IOException {
// try creating missing files
request.file.getParentFile().mkdirs();
Files.createFile(request.file.toPath());
// write default config data
PrintWriter writer = new PrintWriter(request.file, StandardCharsets.UTF_8);
writer.write(request.getConfig());
writer.close();
}
private void loadConfig() throws IOException {
Scanner reader = new Scanner(request.file);
for (int line = 1; reader.hasNextLine(); line++) {
parseConfigEntry(reader.nextLine(), line);
}
}
private void parseConfigEntry(String entry, int line) {
if (!entry.isEmpty() && !entry.startsWith("#")) {
String[] parts = entry.split("=", 2);
if (parts.length == 2) {
config.put(parts[0].trim(), parts[1].trim());
} else {
throw new RuntimeException("Syntax error in config file on line " + line + "!");
}
}
} }
/** /**
* Queries a value from config, returns `null` if the * Loads the config from the filesystem.
* key does not exist.
* *
* @return value corresponding to the given key * @return config object
* @see SimpleConfig#getOrDefault * @see SimpleConfig
*/ */
@Deprecated public SimpleConfig request() {
public String get(String key) { return new SimpleConfig(this);
return config.get(key);
} }
/** private String getConfig() {
* Returns string value from config corresponding to the given return provider.get(filename) + "\n";
* key, or the default string if the key is missing.
*
* @return value corresponding to the given key, or the default value
*/
public String getOrDefault(String key, String def) {
String val = get(key);
return val == null ? def : val;
} }
/** private Map<String, String> getConfigMap() {
* Returns integer value from config corresponding to the given return provider.getMap();
* key, or the default integer if the key is missing or invalid.
*
* @return value corresponding to the given key, or the default value
*/
public int getOrDefault(String key, int def) {
try {
return Integer.parseInt(get(key));
} catch (Exception e) {
return def;
}
} }
/** }
* Returns boolean value from config corresponding to the given
* key, or the default boolean if the key is missing.
*
* @return value corresponding to the given key, or the default value
*/
public boolean getOrDefault(String key, boolean def) {
String val = get(key);
if (val != null) {
return val.equalsIgnoreCase("true");
}
return def;
}
/**
* Returns double value from config corresponding to the given
* key, or the default string if the key is missing or invalid.
*
* @return value corresponding to the given key, or the default value
*/
public double getOrDefault(String key, double def) {
try {
return Double.parseDouble(get(key));
} catch (Exception e) {
return def;
}
}
/**
* If any error occurred during loading or reading from the config
* a 'broken' flag is set, indicating that the config's state
* is undefined and should be discarded using `delete()`
*
* @return the 'broken' flag of the configuration
*/
public boolean isBroken() {
return broken;
}
/**
* deletes the config file from the filesystem
*
* @return true if the operation was successful
*/
public boolean delete() {
LOGGER.warn("Config '" + request.filename + "' was removed from existence! Restart the game to regenerate it.");
return request.file.delete();
}
public interface DefaultConfig {
static String empty(String namespace) {
return "";
}
String get(String namespace);
default Map<String, String> getMap() {
return new HashMap<>();
}
}
public static class ConfigRequest {
private final File file;
private final String filename;
private DefaultConfig provider;
private ConfigRequest(File file, String filename) {
this.file = file;
this.filename = filename;
this.provider = DefaultConfig::empty;
}
/**
* Sets the default config provider, used to generate the
* config if it's missing.
*
* @param provider default config provider
* @return current config request object
* @see DefaultConfig
*/
public ConfigRequest provider(DefaultConfig provider) {
this.provider = provider;
return this;
}
/**
* Loads the config from the filesystem.
*
* @return config object
* @see SimpleConfig
*/
public SimpleConfig request() {
return new SimpleConfig(this);
}
private String getConfig() {
return provider.get(filename) + "\n";
}
private Map<String, String> getConfigMap() {
return provider.getMap();
}
}
} }

View file

@ -1,6 +1,6 @@
package cc.toph.simplycompat.item; package cc.toph.simplycompat.item;
import cc.toph.simplycompat.config.Config.WeaponAttributes; import cc.toph.simplycompat.registry.ConfigRegistry.WEAPON_ATTRIBUTES;
import com.google.common.base.Suppliers; import com.google.common.base.Suppliers;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
@ -12,8 +12,8 @@ import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier; import java.util.function.Supplier;
public enum SimplyCompatToolMaterials implements Tier { public enum SimplyCompatToolMaterials implements Tier {
COPPER(1, 125, 4.5F, 1.0F, WeaponAttributes.COPPER, 8, Items.COPPER_INGOT), COPPER(1, 125, 4.5F, 1.0F, WEAPON_ATTRIBUTES.COPPER, 8, Items.COPPER_INGOT),
STEEL(2, 600, 6.5F, 2.5F, WeaponAttributes.STEEL, 12, Items.DIAMOND); STEEL(2, 600, 6.5F, 2.5F, WEAPON_ATTRIBUTES.STEEL, 12, Items.DIAMOND);
private final int level; private final int level;
private final int uses; private final int uses;
@ -24,13 +24,13 @@ public enum SimplyCompatToolMaterials implements Tier {
private final Supplier<Ingredient> repairIngredient; private final Supplier<Ingredient> repairIngredient;
SimplyCompatToolMaterials( SimplyCompatToolMaterials(
int level, int level,
int uses, int uses,
float speed, float speed,
float attackDamageBonus, float attackDamageBonus,
float damageModifier, float damageModifier,
int enchantmentValue, int enchantmentValue,
Item... repairIngredient Item... repairIngredient
) { ) {
this.level = level; this.level = level;
this.uses = uses; this.uses = uses;
@ -58,7 +58,7 @@ public enum SimplyCompatToolMaterials implements Tier {
*/ */
public String getIdentifier() { public String getIdentifier() {
return BuiltInRegistries.ITEM.getKey( return BuiltInRegistries.ITEM.getKey(
this.repairIngredient.get().getItems()[0].getItem() this.repairIngredient.get().getItems()[0].getItem()
).toString(); ).toString();
} }

View file

@ -0,0 +1,37 @@
package cc.toph.simplycompat.registry;
import cc.toph.simplycompat.SimplyCompat;
import cc.toph.simplycompat.config.Config;
import cc.toph.simplycompat.config.ConfigProvider;
public class ConfigRegistry {
public static final class WEAPON_ATTRIBUTES {
public static float COPPER;
public static float STEEL;
public static final ConfigProvider PROVIDER = new ConfigProvider("weapon_attributes");
public static void register() {
Config common = new Config(PROVIDER);
COPPER = common.registerProp(
"copper_damageModifier",
3.0f,
"Copper Damage Modifier"
);
STEEL = common.registerProp(
"steel_damageModifier",
5.0f,
"Steel Damage Modifier"
);
common.register();
}
}
public static void registerConfigs() {
WEAPON_ATTRIBUTES.register();
}
}

View file

@ -9,42 +9,42 @@ import net.minecraft.world.item.Item;
import net.sweenus.simplyswords.item.SimplySwordsSwordItem; import net.sweenus.simplyswords.item.SimplySwordsSwordItem;
public class ItemsRegistry { public class ItemsRegistry {
public static final DeferredRegister<Item> ITEMS = DeferredRegister.create( public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(
SimplyCompat.MOD_ID, SimplyCompat.MOD_ID,
Registries.ITEM Registries.ITEM
);
private static void registerSword(
SimplyCompatToolMaterials material,
WeaponType type
) {
float finalDamage = material.getDamageModifier() + type.getPositiveModifier() - type.getNegativeModifier();
String name = material.getName() + "_" + type.getWeaponNameSuffix();
ITEMS.register(name, () ->
new SimplySwordsSwordItem(material, (int) finalDamage, type.getAttackSpeed(), material.getIdentifier())
); );
}
private static void registerSword( /**
SimplyCompatToolMaterials material, * Registers all sword items. Iterate through all available weapon types
WeaponType type * and tool materials. For each combination, the corresponding sword
) { * is registered using the private {@code registerSword} method.
* <br>
* This ensures that each defined material and weapon type combination is
* registered within the item registry for use within the mod.
*/
public static void registerAllSwords() {
// TODO: Register Compat Swords
float finalDamage = material.getDamageModifier() + type.getPositiveModifier() - type.getNegativeModifier(); // Register "Vanilla" swords
String name = material.getName() + "_" + type.getWeaponNameSuffix(); for (WeaponType type : WeaponType.values()) {
for (SimplyCompatToolMaterials material : SimplyCompatToolMaterials.values()) {
ITEMS.register(name, () -> registerSword(material, type);
new SimplySwordsSwordItem(material, (int) finalDamage, type.getAttackSpeed(), material.getIdentifier()) }
);
} }
/** ITEMS.register();
* Registers all sword items. Iterate through all available weapon types }
* and tool materials. For each combination, the corresponding sword
* is registered using the private {@code registerSword} method.
* <br>
* This ensures that each defined material and weapon type combination is
* registered within the item registry for use within the mod.
*/
public static void registerAllSwords() {
// TODO: Register Compat Swords
// Register "Vanilla" swords
for (WeaponType type : WeaponType.values()) {
for (SimplyCompatToolMaterials material : SimplyCompatToolMaterials.values()) {
registerSword(material, type);
}
}
ITEMS.register();
}
} }