diff --git a/build.gradle b/build.gradle index 77187f8..42e9345 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,8 @@ dependencies { [ "fabric-api-base", "fabric-registry-sync-v0", - "fabric-lifecycle-events-v1" + "fabric-lifecycle-events-v1", + "fabric-networking-api-v1" ].forEach { modImplementation fabricApi.module(it, project.fabric_api_version) } modCompileOnly "io.github.fourmisain:TaxFreeLevels-fabric:$project.tax_free_levels_version" diff --git a/gradle.properties b/gradle.properties index a8b476f..47f8dd0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,5 +14,5 @@ org.gradle.jvmargs=-Xmx1G # Dependencies fabric_api_version=0.110.0+1.21.4 - codec_config_api_version=2.1.0+1.21.3 + codec_config_api_version=3.0.0+1.21.3 tax_free_levels_version=1.4.1-fabric-1.21.1 diff --git a/src/main/java/de/mschae23/grindenchantments/GrindEnchantmentsMod.java b/src/main/java/de/mschae23/grindenchantments/GrindEnchantmentsMod.java index c881ba8..e3d3f5e 100644 --- a/src/main/java/de/mschae23/grindenchantments/GrindEnchantmentsMod.java +++ b/src/main/java/de/mschae23/grindenchantments/GrindEnchantmentsMod.java @@ -25,15 +25,28 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; +import net.minecraft.registry.RegistryOps; +import net.minecraft.registry.RegistryWrapper; import net.minecraft.util.Identifier; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; +import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; import net.fabricmc.loader.api.FabricLoader; +import com.google.gson.JsonElement; import com.mojang.serialization.Codec; +import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; -import com.mojang.serialization.MapCodec; +import de.mschae23.config.api.ConfigIo; import de.mschae23.config.api.ModConfig; import de.mschae23.config.api.exception.ConfigException; -import de.mschae23.config.impl.ConfigUtil; +import de.mschae23.grindenchantments.config.ClientConfig; +import de.mschae23.grindenchantments.config.ServerConfig; import de.mschae23.grindenchantments.config.legacy.v1.GrindEnchantmentsConfigV1; import de.mschae23.grindenchantments.config.legacy.v2.GrindEnchantmentsConfigV2; import de.mschae23.grindenchantments.config.legacy.v3.GrindEnchantmentsConfigV3; @@ -43,6 +56,7 @@ import de.mschae23.grindenchantments.impl.DisenchantOperation; import de.mschae23.grindenchantments.impl.MoveOperation; import de.mschae23.grindenchantments.impl.ResetRepairCostOperation; +import de.mschae23.grindenchantments.network.s2c.ServerConfigS2CPayload; import de.mschae23.grindenchantments.registry.GrindEnchantmentsRegistries; import io.github.fourmisain.taxfreelevels.TaxFreeLevels; import org.apache.logging.log4j.Level; @@ -55,9 +69,32 @@ public class GrindEnchantmentsMod implements ModInitializer { public static final Logger LOGGER = LogManager.getLogger("Grind Enchantments"); private static GrindEnchantmentsConfigV3 LEGACY_CONFIG = GrindEnchantmentsConfigV3.DEFAULT; + @Nullable + private static ServerConfig SERVER_CONFIG = null; + @Nullable + private static ClientConfig CLIENT_CONFIG = null; @Override public void onInitialize() { + // Singleplayer + ServerLifecycleEvents.SERVER_STARTING.register(server -> readServerConfig(server.getRegistryManager()) + .ifPresent(config -> SERVER_CONFIG = config)); + ServerLifecycleEvents.SERVER_STOPPING.register(server -> SERVER_CONFIG = null); + + // Multiplayer + PayloadTypeRegistry.configurationS2C().register(ServerConfigS2CPayload.ID, ServerConfigS2CPayload.CODEC); + + ClientLifecycleEvents.CLIENT_STARTED.register(client -> { + CLIENT_CONFIG = GrindEnchantmentsMod.readClientConfig().orElse(ClientConfig.DEFAULT); + + ClientConfigurationNetworking.registerGlobalReceiver(ServerConfigS2CPayload.ID, (payload, context) -> { + //noinspection resource + context.client().execute(() -> { + // TODO + }); + }); + }); + LEGACY_CONFIG = readLegacyConfig().orElse(GrindEnchantmentsConfigV3.DEFAULT); GrindEnchantmentsRegistries.init(); @@ -85,14 +122,61 @@ public void onInitialize() { } } - private static > ModConfig.Type getConfigType(int versionOffset, MapCodec>[] codecs, int version) { - for (int i = codecs.length; i > 0; i--) { - if (version == i) { - return new ModConfig.Type<>(i + versionOffset, codecs[i - 1]); + public static ServerConfig getServerConfig() { + return SERVER_CONFIG == null ? ServerConfig.DEFAULT : SERVER_CONFIG; + } + + public static ClientConfig getClientConfig() { + return CLIENT_CONFIG == null ? ClientConfig.DEFAULT : CLIENT_CONFIG; + } + + @Deprecated + public static GrindEnchantmentsConfigV3 getLegacyConfig() { + return LEGACY_CONFIG; + } + + private static > ModConfig.Type> getConfigType(ModConfig.Type>[] versions, int version) { + for (int i = versions.length - 1; i >= 0; i--) { + ModConfig.Type> v = versions[i]; + + if (version == v.version()) { + return v; + } + } + + return versions[versions.length - 1]; + } + + private static > Optional readGenericConfig(Path configName, Codec> codec, + DynamicOps ops, String kind) { + Path filePath = FabricLoader.getInstance().getConfigDir().resolve(MODID).resolve(configName); + @Nullable + C config = null; + + if (Files.exists(filePath) && Files.isRegularFile(filePath)) { + try (InputStream input = Files.newInputStream(filePath)) { + log(Level.INFO, "Reading " + kind + " config."); + + ModConfig readConfig = ConfigIo.decodeConfig(input, codec, ops); + config = readConfig.latest(); + } catch (IOException e) { + log(Level.ERROR, "IO exception while trying to read " + kind + " config: " + e.getLocalizedMessage()); + } catch (ConfigException e) { + log(Level.ERROR, e.getLocalizedMessage()); } } - return new ModConfig.Type<>(codecs.length + versionOffset, codecs[codecs.length - 1]); + return Optional.ofNullable(config); + } + + public static Optional readClientConfig() { + return readGenericConfig(Path.of("client.json"), ModConfig.createCodec(ClientConfig.TYPE.version(), version -> + getConfigType(ClientConfig.VERSIONS, version)), JsonOps.INSTANCE, "client"); + } + + private static Optional readServerConfig(RegistryWrapper.WrapperLookup wrapperLookup) { + return readGenericConfig(Path.of("server.json"), ModConfig.createCodec(ServerConfig.TYPE.version(), version -> + getConfigType(ServerConfig.VERSIONS, version)), RegistryOps.of(JsonOps.INSTANCE, wrapperLookup), "server"); } @SuppressWarnings("deprecation") @@ -100,14 +184,12 @@ private static Optional readLegacyConfig() { final GrindEnchantmentsConfigV3 legacyLatestConfigDefault = GrindEnchantmentsConfigV3.DEFAULT; final int legacyLatestConfigVersion = legacyLatestConfigDefault.version(); @SuppressWarnings({"unchecked", "deprecation"}) - final MapCodec>[] legacyConfigCodecs = new MapCodec[] { - GrindEnchantmentsConfigV1.TYPE_CODEC, GrindEnchantmentsConfigV2.TYPE_CODEC, GrindEnchantmentsConfigV3.TYPE_CODEC + final ModConfig.Type>[] legacyConfigCodecs = new ModConfig.Type[] { + GrindEnchantmentsConfigV1.TYPE, GrindEnchantmentsConfigV2.TYPE, GrindEnchantmentsConfigV3.TYPE }; final Codec> legacyConfigCodec = ModConfig.createCodec(legacyLatestConfigVersion, version -> - getConfigType(0, legacyConfigCodecs, version)); - - // Unfortunately, this requires some manual work and usage of codec config API's internals + getConfigType(legacyConfigCodecs, version)); Path configPath = FabricLoader.getInstance().getConfigDir().resolve(Paths.get(MODID + ".json")); @Nullable @@ -117,8 +199,7 @@ private static Optional readLegacyConfig() { try (InputStream input = Files.newInputStream(configPath)) { log(Level.INFO, "Reading legacy config."); - @SuppressWarnings("UnstableApiUsage") - ModConfig readConfig = ConfigUtil.decodeConfig(input, legacyConfigCodec, JsonOps.INSTANCE); + ModConfig readConfig = ConfigIo.decodeConfig(input, legacyConfigCodec, JsonOps.INSTANCE); config = readConfig.latest(); } catch (IOException e) { log(Level.ERROR, "IO exception while trying to read config: " + e.getLocalizedMessage()); @@ -130,10 +211,6 @@ private static Optional readLegacyConfig() { return Optional.ofNullable(config); } - public static GrindEnchantmentsConfigV3 getConfig() { - return LEGACY_CONFIG; - } - public static void log(Level level, Object message) { LOGGER.log(level, "[Grind Enchantments] {}", message); } diff --git a/src/main/java/de/mschae23/grindenchantments/config/ClientConfig.java b/src/main/java/de/mschae23/grindenchantments/config/ClientConfig.java new file mode 100644 index 0000000..17d05b6 --- /dev/null +++ b/src/main/java/de/mschae23/grindenchantments/config/ClientConfig.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 mschae23 + * + * This file is part of Grind enchantments. + * + * Grind enchantments is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package de.mschae23.grindenchantments.config; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.mschae23.config.api.ModConfig; + +public record ClientConfig(boolean showLevelCost) implements ModConfig { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + Codec.BOOL.fieldOf("show_enchantment_cost").forGetter(ClientConfig::showLevelCost) + ).apply(instance, instance.stable(ClientConfig::new))); + + public static final ModConfig.Type TYPE = new ModConfig.Type<>(4, CODEC); + public static final ClientConfig DEFAULT = new ClientConfig(true); + + @SuppressWarnings("unchecked") + public static final ModConfig.Type>[] VERSIONS = new ModConfig.Type[] { TYPE, }; + + @Override + public Type type() { + return TYPE; + } + + @Override + public ClientConfig latest() { + return this; + } + + @Override + public boolean shouldUpdate() { + return true; + } +} diff --git a/src/main/java/de/mschae23/grindenchantments/config/DedicatedServerConfig.java b/src/main/java/de/mschae23/grindenchantments/config/DedicatedServerConfig.java new file mode 100644 index 0000000..a289885 --- /dev/null +++ b/src/main/java/de/mschae23/grindenchantments/config/DedicatedServerConfig.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 mschae23 + * + * This file is part of Grind enchantments. + * + * Grind enchantments is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package de.mschae23.grindenchantments.config; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +public record DedicatedServerConfig(boolean alternativeCostDisplay) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.BOOL.fieldOf("alternative_cost_display_enabled").forGetter(DedicatedServerConfig::alternativeCostDisplay) + ).apply(instance, instance.stable(DedicatedServerConfig::new))); + + public static final DedicatedServerConfig DEFAULT = new DedicatedServerConfig(false); +} diff --git a/src/main/java/de/mschae23/grindenchantments/config/ServerConfig.java b/src/main/java/de/mschae23/grindenchantments/config/ServerConfig.java new file mode 100644 index 0000000..7003a2d --- /dev/null +++ b/src/main/java/de/mschae23/grindenchantments/config/ServerConfig.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 mschae23 + * + * This file is part of Grind enchantments. + * + * Grind enchantments is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package de.mschae23.grindenchantments.config; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import de.mschae23.config.api.ModConfig; + +public record ServerConfig(DisenchantConfig disenchant, MoveConfig move, ResetRepairCostConfig resetRepairCost, + FilterConfig filter, + DedicatedServerConfig dedicatedServerConfig) implements ModConfig { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + DisenchantConfig.CODEC.fieldOf("disenchant_to_book").forGetter(ServerConfig::disenchant), + MoveConfig.CODEC.fieldOf("move_enchantments").forGetter(ServerConfig::move), + ResetRepairCostConfig.CODEC.fieldOf("reset_repair_cost").forGetter(ServerConfig::resetRepairCost), + FilterConfig.CODEC.fieldOf("filter").forGetter(ServerConfig::filter), + DedicatedServerConfig.CODEC.orElse(DedicatedServerConfig.DEFAULT).fieldOf("dedicated_server_options").forGetter(ServerConfig::dedicatedServerConfig) + ).apply(instance, instance.stable(ServerConfig::new))); + + public static final ModConfig.Type TYPE = new ModConfig.Type<>(4, CODEC); + public static final ServerConfig DEFAULT = new ServerConfig(DisenchantConfig.DEFAULT, MoveConfig.DEFAULT, + ResetRepairCostConfig.DEFAULT, FilterConfig.DEFAULT, DedicatedServerConfig.DEFAULT); + + @SuppressWarnings("unchecked") + public static final ModConfig.Type>[] VERSIONS = new ModConfig.Type[] { TYPE, }; + + @Override + public Type type() { + return TYPE; + } + + @Override + public ServerConfig latest() { + return this; + } + + @Override + public boolean shouldUpdate() { + return true; + } +} diff --git a/src/main/java/de/mschae23/grindenchantments/network/s2c/ServerConfigS2CPayload.java b/src/main/java/de/mschae23/grindenchantments/network/s2c/ServerConfigS2CPayload.java new file mode 100644 index 0000000..831e65d --- /dev/null +++ b/src/main/java/de/mschae23/grindenchantments/network/s2c/ServerConfigS2CPayload.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 mschae23 + * + * This file is part of Grind enchantments. + * + * Grind enchantments is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package de.mschae23.grindenchantments.network.s2c; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.packet.CustomPayload; +import net.minecraft.util.Identifier; +import de.mschae23.grindenchantments.GrindEnchantmentsMod; + +public record ServerConfigS2CPayload() implements CustomPayload { + public static final Identifier PACKET_ID = GrindEnchantmentsMod.id("server_config"); + public static final CustomPayload.Id ID = new CustomPayload.Id<>(PACKET_ID); + + public static final PacketCodec CODEC = PacketCodec.unit(new ServerConfigS2CPayload()); + + @Override + public Id getId() { + return ID; + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 55a4d9f..b11dc4f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -34,7 +34,9 @@ "java": ">=21", "fabric-api-base": "*", - "fabric-registry-sync-v0": "*" + "fabric-registry-sync-v0": "*", + "fabric-lifecycle-events-v1": "*", + "fabric-networking-api-v1": "*" }, "suggests": { },