diff --git a/OptiGUI-Fabric/src/main/kotlin/opekope2/optigui/internal/fabric/FabricOptiGuiClient.kt b/OptiGUI-Fabric/src/main/kotlin/opekope2/optigui/internal/fabric/FabricOptiGuiClient.kt index 783c9ee1d..d86300e5e 100644 --- a/OptiGUI-Fabric/src/main/kotlin/opekope2/optigui/internal/fabric/FabricOptiGuiClient.kt +++ b/OptiGUI-Fabric/src/main/kotlin/opekope2/optigui/internal/fabric/FabricOptiGuiClient.kt @@ -40,6 +40,9 @@ internal class FabricOptiGuiClient : ScreenEvents.AfterRender { override val version = FabricLoader.getInstance().getModContainer(MOD_ID).getOrNull()?.metadata?.version.toString() + override var isInitialized: Boolean = false + private set + override fun isModInstalled(modId: String) = FabricLoader.getInstance().isModLoaded(modId) override fun onInitializeClient() { @@ -51,6 +54,7 @@ internal class FabricOptiGuiClient : ClientTickEvents.END_WORLD_TICK.register(this) ClientPlayConnectionEvents.DISCONNECT.register(this) ScreenEvents.AFTER_INIT.register(this) + isInitialized = true } override fun registerNbtFilters() { @@ -75,7 +79,7 @@ internal class FabricOptiGuiClient : val textureChanger = FabricResourceReloadListener(id, TextureChanger) { IFilterLoader.map { it.key } } manager.registerReloadListener(textureChanger) - for ((id, loader) in IFilterLoader) { + for (loader in filterLoaders) { manager.registerReloadListener(FabricResourceReloadListener(id, loader, ::emptyList)) } } diff --git a/OptiGUI-Fabric/src/main/resources/fabric.mod.json b/OptiGUI-Fabric/src/main/resources/fabric.mod.json index 33b88ec3c..06f69ea12 100644 --- a/OptiGUI-Fabric/src/main/resources/fabric.mod.json +++ b/OptiGUI-Fabric/src/main/resources/fabric.mod.json @@ -105,9 +105,9 @@ "fabric-language-kotlin": ">=$fabric_language_kotlin", "fabricloader": ">=0.15.0", "java": ">=$java", - "minecraft": ">=$minecraft <1.21.2", + "minecraft": ">=1.21 <1.21.2", "optigui-screen-api": ">=$version", - "owo-lib": ">=$owo_lib" + "owo-lib": ">=$owo_fabric" }, "suggests": { "modmenu": "*", diff --git a/OptiGUI-NeoForge/build.gradle.kts b/OptiGUI-NeoForge/build.gradle.kts new file mode 100644 index 000000000..0567b9b12 --- /dev/null +++ b/OptiGUI-NeoForge/build.gradle.kts @@ -0,0 +1,100 @@ +import opekope2.optigui.buildscript.extension.Version +import opekope2.optigui.buildscript.extension.commonProject +import opekope2.optigui.buildscript.task.GenerateInternalPackageInfos + +plugins { + id("opekope2.optigui.buildscript.plugin.Loader") + alias(libs.plugins.moddev) + kotlin +} + +version = Version.neoForge(libs.versions.optigui, libs.versions.minecraft) + +val commonKotlin by configurations.registering { isCanBeResolved = true } + +base { + archivesName = "optigui-neoforge" +} + +neoForge { + version = libs.versions.neoforge.get() + parchment { + minecraftVersion = libs.versions.minecraft + mappingsVersion = libs.versions.parchment + } + + runs { + register("client") { + client() + ideName = "NeoForge Client ($path)" + gameDirectory = file("../run") + } + } + + mods { + register("optigui") { + sourceSet(sourceSets["main"]) + } + } +} + +repositories { + exclusiveContent { + forRepository { maven("https://thedarkcolour.github.io/KotlinForForge/") { name = "Kotlin for Forge" } } + filter { includeGroup("thedarkcolour") } + } +} + +dependencies { + // https://discord.com/channels/313125603924639766/1185197721477468160/1462863046710923422 + implementation(libs.kfflang) + implementation(libs.kfflib) + implementation(libs.kffmod) + + commonProject(":OptiGUI", kotlin = true) + + api(libs.owo.lib.neoforge) + accessTransformers(libs.owo.lib.neoforge) + interfaceInjectionData(libs.owo.lib.neoforge) + + runtimeOnly(libs.ini4j) + runtimeOnly(libs.runefox.json) + runtimeOnly(libs.runefox.jsonkt) + runtimeOnly(project(":ScreenAPI-NeoForge")) + runtimeOnly(project(":ScreenNBT-NeoForge")) + + "additionalRuntimeClasspath"(libs.ini4j) + "additionalRuntimeClasspath"(libs.runefox.json) + "additionalRuntimeClasspath"(libs.runefox.jsonkt) + "additionalRuntimeClasspath"(libs.kotlin.stdlib) + "additionalRuntimeClasspath"(libs.kotlin.reflect) + + jarJar(libs.ini4j) + jarJar(libs.runefox.json) + jarJar(libs.runefox.jsonkt) + jarJar(project(":ScreenAPI-NeoForge")) + jarJar(project(":ScreenNBT-NeoForge")) +} + +tasks { + compileKotlin { + dependsOn(commonKotlin, codegen) + source(commonKotlin) + } + + sourcesJar { + dependsOn(commonKotlin) + from(commonKotlin) + } + + val generateInternalPackageInfos by registering(GenerateInternalPackageInfos::class) { + sourceRoot = projectDir.resolve("src/main/kotlin") + // language=RegExp + packageMatcher("""^opekope2\.optigui\.internal\.neoforge(\..+)?$""") + } + + codegen { dependsOn(generateInternalPackageInfos, ":OptiGUI:codegen") } + + sourceSets.main.get().java.srcDir(generateInternalPackageInfos) + artifacts.commonJava(generateInternalPackageInfos) +} diff --git a/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/NeoForgeOptiGuiClient.kt b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/NeoForgeOptiGuiClient.kt new file mode 100644 index 000000000..46e9635a2 --- /dev/null +++ b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/NeoForgeOptiGuiClient.kt @@ -0,0 +1,113 @@ +package opekope2.optigui.internal.neoforge + +import com.google.common.base.Suppliers +import net.minecraft.client.gui.screens.Screen +import net.neoforged.api.distmarker.Dist +import net.neoforged.bus.api.EventPriority +import net.neoforged.bus.api.SubscribeEvent +import net.neoforged.fml.ModContainer +import net.neoforged.fml.ModList +import net.neoforged.fml.common.Mod +import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent +import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent +import net.neoforged.neoforge.client.event.ScreenEvent +import net.neoforged.neoforge.client.gui.IConfigScreenFactory +import net.neoforged.neoforge.event.tick.LevelTickEvent +import opekope2.optigui.config.config +import opekope2.optigui.filter.INbtFilter +import opekope2.optigui.interaction.InteractionManager +import opekope2.optigui.internal.AbstractOptiGuiClient +import opekope2.optigui.internal.TextureChanger +import opekope2.optigui.internal.neoforge.event_handler.NeoForgeAttackHandler +import opekope2.optigui.internal.neoforge.event_handler.NeoForgeInteractionHandler +import opekope2.optigui.internal.neoforge.filter.NbtVersionFilter +import opekope2.optigui.internal.neoforge.gui.widget.NeoForgeInspectorWidget +import opekope2.optigui.internal.neoforge.nbt_provider.NeoForgeModsNbtProvider +import opekope2.optigui.internal.ui.ConfigScreen +import opekope2.optigui.nbt_provider.ILoadTimeNbtProvider +import opekope2.optigui.screen_api.screen.ITextureChangeableScreen +import opekope2.optigui.util.MOD_ID +import thedarkcolour.kotlinforforge.neoforge.forge.FORGE_BUS +import thedarkcolour.kotlinforforge.neoforge.forge.MOD_BUS +import kotlin.jvm.optionals.getOrDefault + +@Mod(MOD_ID, dist = [Dist.CLIENT]) +internal class NeoForgeOptiGuiClient(modContainer: ModContainer) : AbstractOptiGuiClient(), IConfigScreenFactory { + init { + super.initialize() + + FORGE_BUS.register(NeoForgeInteractionHandler) + FORGE_BUS.register(NeoForgeAttackHandler) + FORGE_BUS.register(this) + MOD_BUS.addListener(::registerFilterLoaders) + // Ensure that all other filter loaders finished applying the loaded resources and set IFilterLoader.filters + MOD_BUS.addListener(EventPriority.LOWEST, ::registerTextureChangerFilterLoader) + modContainer.registerExtensionPoint(IConfigScreenFactory::class.java, this) + } + + val issueTrackerUrlGetter = Suppliers.memoize { + modContainer.modInfo.owningFile.config.getConfigElement("issueTrackerURL") + .getOrDefault("(failed to read issue tracker URL)") + } + + override val version = modContainer.modInfo.version.toString() + + override val isInitialized: Boolean = true // Only false if something in the constructor crashes + + override fun isModInstalled(modId: String) = ModList.get().isLoaded(modId) + + override fun registerNbtFilters() { + super.registerNbtFilters() + INbtFilter.register(">v", NbtVersionFilter.Type.VERSION_GREATER) + INbtFilter.register(">=v", NbtVersionFilter.Type.VERSION_GREATER_EQUAL) + INbtFilter.register("=v", NbtVersionFilter.Type.VERSION_EQUAL) + INbtFilter.register("!=v", NbtVersionFilter.Type.VERSION_NOT_EQUAL) + INbtFilter.register("<=v", NbtVersionFilter.Type.VERSION_LESS_EQUAL) + INbtFilter.register("(JavaOps.INSTANCE) { + override fun convertTo(outOps: DynamicOps, input: Any): U = when (input) { + is Map<*, *> -> convertMap(outOps, input) + is ByteList, is IntList, is LongList -> super.convertTo(outOps, input) + is List<*> -> convertList(outOps, input) + + is Config -> convertMap(outOps, input.valueMap()) + is Temporal -> convertMap(outOps, input.getChronoFields()) + is NullObject -> outOps.empty() + is Collection<*> -> convertList(outOps, input.toList()) + is Enum<*> -> outOps.createString(input.name) + + else -> super.convertTo(outOps, input) + } + + private fun TemporalAccessor.getChronoFields(): Object2LongMap = + ChronoField.entries.fold(Object2LongArrayMap(ChronoField.entries.size)) { acc, field -> + acc[field.toString()] = getLong(field) + acc + } +} diff --git a/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/event_handler/NeoForgeAttackHandler.kt b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/event_handler/NeoForgeAttackHandler.kt new file mode 100644 index 000000000..9f3cb2032 --- /dev/null +++ b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/event_handler/NeoForgeAttackHandler.kt @@ -0,0 +1,45 @@ +package opekope2.optigui.internal.neoforge.event_handler + +import net.minecraft.world.InteractionHand +import net.neoforged.bus.api.SubscribeEvent +import net.neoforged.neoforge.event.entity.player.AttackEntityEvent +import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent +import opekope2.optigui.config.config +import opekope2.optigui.interaction.* + +internal object NeoForgeAttackHandler { + @SubscribeEvent + fun attackBlock(event: PlayerInteractEvent.LeftClickBlock) { + if (!event.level.isClientSide) return + if (!config.interactWithAttackKey()) return + + val pos = event.pos + val world = event.level + val player = event.entity + val hand = event.hand + + InteractionManager.prepare( + BlockInteraction.factory(pos, world.getBlockState(pos), world.getBlockEntity(pos), player, hand) + ) + } + + @SubscribeEvent + fun attackEntity(event: AttackEntityEvent) { + if (!event.entity.level().isClientSide) return + if (!config.interactWithAttackKey()) return + + InteractionManager.prepare(EntityInteraction.factory(event.target, event.entity, InteractionHand.MAIN_HAND)) + } + + @SubscribeEvent + fun attackWithItem(event: PlayerInteractEvent.LeftClickEmpty) { + if (!event.level.isClientSide) return + if (!config.interactWithAttackKey()) return + + val player = event.entity + + InteractionManager.prepare( + GeneralInteraction.factory(InteractionTarget.Item(player.mainHandItem), player, InteractionHand.MAIN_HAND) + ) + } +} diff --git a/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/event_handler/NeoForgeInteractionHandler.kt b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/event_handler/NeoForgeInteractionHandler.kt new file mode 100644 index 000000000..1f339c5c2 --- /dev/null +++ b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/event_handler/NeoForgeInteractionHandler.kt @@ -0,0 +1,43 @@ +package opekope2.optigui.internal.neoforge.event_handler + +import net.neoforged.bus.api.SubscribeEvent +import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent +import opekope2.optigui.interaction.* + +internal object NeoForgeInteractionHandler { + @SubscribeEvent + fun interactWithBlock(event: PlayerInteractEvent.RightClickBlock) { + if (!event.level.isClientSide) return + + val world = event.level + val blockPos = event.pos + val blockState = world.getBlockState(blockPos) + val blockEntity = world.getBlockEntity(blockPos) + val player = event.entity + val hand = event.hand + + InteractionManager.prepare(BlockInteraction.factory(blockPos, blockState, blockEntity, player, hand)) + } + + @SubscribeEvent + fun interactWithEntity(event: PlayerInteractEvent.EntityInteractSpecific) { + if (!event.level.isClientSide) return + + val entity = event.target + val player = event.entity + val hand = event.hand + + InteractionManager.prepare(EntityInteraction.factory(entity, player, hand)) + } + + @SubscribeEvent + fun interactWithItem(event: PlayerInteractEvent.RightClickItem) { + if (!event.level.isClientSide) return + + val player = event.entity + val hand = event.hand + val stack = player.getItemInHand(hand) + + InteractionManager.prepare(GeneralInteraction.factory(InteractionTarget.Item(stack), player, hand)) + } +} diff --git a/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/filter/NbtVersionFilter.kt b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/filter/NbtVersionFilter.kt new file mode 100644 index 000000000..443b28d6a --- /dev/null +++ b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/filter/NbtVersionFilter.kt @@ -0,0 +1,49 @@ +package opekope2.optigui.internal.neoforge.filter + +import com.mojang.serialization.Codec +import net.minecraft.nbt.StringTag +import net.minecraft.nbt.Tag +import opekope2.optigui.filter.INbtFilter +import opekope2.optigui.filter.comparer.INbtComparer +import opekope2.optigui.filter.comparer.INbtComparer.ComparisonResult.* +import org.apache.maven.artifact.versioning.ArtifactVersion +import org.apache.maven.artifact.versioning.DefaultArtifactVersion +import java.util.* + +internal class NbtVersionFilter(private val version: ArtifactVersion, override val type: Type) : INbtFilter { + override fun test(nbt: Tag, root: Tag): Boolean { + if (nbt !is StringTag) return false + val nbtVersion = DefaultArtifactVersion(nbt.asString) + return type.test(nbtVersion, version) + } + + override fun asString() = super.asString() + " " + version.toString() + + enum class Type( + val acceptedResults: EnumSet, + val requireSameMajor: Boolean = false, + val requireSameMinor: Boolean = false + ) : INbtFilter.IType { + VERSION_GREATER_EQUAL(MORE, EQUAL), + VERSION_LESS_EQUAL(LESS, EQUAL), + VERSION_GREATER(MORE), + VERSION_LESS(LESS), + VERSION_EQUAL(EQUAL), + VERSION_SAME_TO_NEXT_MINOR(EnumSet.of(MORE, EQUAL), requireSameMajor = true), + VERSION_SAME_TO_NEXT_MAJOR(EnumSet.of(MORE, EQUAL), requireSameMajor = true, requireSameMinor = true), + VERSION_NOT_EQUAL(MORE, LESS); + + constructor(vararg acceptedResults: INbtComparer.ComparisonResult) : + this(acceptedResults.toCollection(EnumSet.noneOf(INbtComparer.ComparisonResult::class.java))) + + override val codec: Codec = Codec.STRING.xmap( + { NbtVersionFilter(DefaultArtifactVersion(it), this) }, + { it.version.toString() } + ) + + fun test(version: ArtifactVersion, reference: ArtifactVersion) = + INbtComparer.ComparisonResult.ofComparison(reference.compareTo(version)) in acceptedResults && + (!requireSameMajor || version.majorVersion == reference.majorVersion) && + (!requireSameMinor || version.minorVersion == reference.minorVersion) + } +} diff --git a/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/gui/widget/NeoForgeInspectorWidget.kt b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/gui/widget/NeoForgeInspectorWidget.kt new file mode 100644 index 000000000..1738b38e8 --- /dev/null +++ b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/gui/widget/NeoForgeInspectorWidget.kt @@ -0,0 +1,24 @@ +package opekope2.optigui.internal.neoforge.gui.widget + +import net.neoforged.bus.api.SubscribeEvent +import net.neoforged.neoforge.client.event.ScreenEvent +import opekope2.optigui.internal.inspector.InspectorWidget +import opekope2.optigui.screen_api.screen.ITextureChangeableScreen +import thedarkcolour.kotlinforforge.neoforge.forge.FORGE_BUS + +internal class NeoForgeInspectorWidget : InspectorWidget() { + init { + FORGE_BUS.register(this) + } + + @SubscribeEvent + fun beforeRender(event: ScreenEvent.Render.Pre) { + val screen = event.screen + if (screen is ITextureChangeableScreen) screen.optiGui_positionInspectorWidget(this) + } + + @SubscribeEvent + fun onScreenClosing(event: ScreenEvent.Closing) { + FORGE_BUS.unregister(this) + } +} diff --git a/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/nbt_provider/NeoForgeModsNbtProvider.kt b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/nbt_provider/NeoForgeModsNbtProvider.kt new file mode 100644 index 000000000..05d94055e --- /dev/null +++ b/OptiGUI-NeoForge/src/main/kotlin/opekope2/optigui/internal/neoforge/nbt_provider/NeoForgeModsNbtProvider.kt @@ -0,0 +1,45 @@ +package opekope2.optigui.internal.neoforge.nbt_provider + +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps +import net.neoforged.fml.ModList +import net.neoforged.neoforgespi.language.IModInfo +import opekope2.optigui.internal.neoforge.NeoForgeOptiGuiClient +import opekope2.optigui.internal.neoforge.dfu.NightConfigOps +import opekope2.optigui.nbt_provider.ILoadTimeNbtProvider +import org.slf4j.LoggerFactory +import kotlin.jvm.optionals.getOrNull + +internal class NeoForgeModsNbtProvider(private val mod: NeoForgeOptiGuiClient) : ILoadTimeNbtProvider { + private val logger = LoggerFactory.getLogger(NeoForgeModsNbtProvider::class.java) + private val suppressedModErrors = mutableSetOf() + + override fun get() = CompoundTag().apply { + for (mod in ModList.get().mods) { + put(mod.modId, CompoundTag().apply { + putString("version", mod.version.toString()) + }) + } + } + + private fun getModProperties(mod: IModInfo) = try { + NightConfigOps.convertTo(NbtOps.INSTANCE, mod.modProperties) + } catch (e: Exception) { + if (mod.modId !in suppressedModErrors) { + logError(mod, e) + suppressedModErrors += mod.modId // Do not spam console each time evaluated + } + null + } + + private fun logError(mod: IModInfo, error: Exception) { + logger.atError() + .setCause(error) + .addArgument(mod.displayName) + .addArgument(mod.modId) + .addArgument(mod.version) + .addArgument(mod.modURL::getOrNull) + .addArgument(this.mod.issueTrackerUrlGetter) + .log("Error encoding modproperties of mod {} (modId={},version={},modURL={}). THIS IS A BUG. Please report this at OptiGUI issue tracker: {}") + } +} diff --git a/OptiGUI-NeoForge/src/main/resources/META-INF/neoforge.mods.toml b/OptiGUI-NeoForge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 000000000..3140a04ec --- /dev/null +++ b/OptiGUI-NeoForge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,59 @@ +modLoader = "kotlinforforge" +loaderVersion = "[$kotlin_for_forge,)" +issueTrackerURL = "https://github.com/opekope2/OptiGUI/issues" +license = "LGPL-3.0-or-later" + +[[mods]] +modId = "optigui" +version = "$version" +displayName = "OptiGUI" +description = ''' +Blazing fast custom GUI textures on Fabric and Quilt with built-in OptiFine custom GUI resource pack support +''' +logoFile = "icon.png" +logoBlur = false +features = { java_version = "[$java,)" } +credits = ''' +Creeperucan +dirtTW +gorr0w7 +Lucanoria +lumiscosity +LyriaWintona +Nadios_kox +notlin4 +NuruddinPlays +RoberbufDx8105 +Texaliuz +''' +authors = "opekope2" +displayURL = "https://opekope2.dev/OptiGUI" + +[[mixins]] +config = "optigui.mixins.json" + +[[dependencies.optigui]] +modId = "kotlinforforge" +versionRange = "[$kotlin_for_forge,)" + +[[dependencies.optigui]] +modId = "minecraft" +versionRange = "[$minecraft]" + +[[dependencies.optigui]] +modId = "neoforge" +versionRange = "[$neoforge,)" + +[[dependencies.optigui]] +modId = "optigui_screen_api" +versionRange = "[$version,)" + +[[dependencies.optigui]] +modId = "owo" +versionRange = "[$owo_neoforge,)" + +[mc-publish] +dependencies = [ + "owo-lib@$owo_neoforge", + "kotlin-for-forge@$kotlin_for_forge" +] diff --git a/OptiGUI/src/main/java/opekope2/optigui/mixin/MinecraftMixin.java b/OptiGUI/src/main/java/opekope2/optigui/mixin/MinecraftMixin.java index cbe1ee02f..75a268c74 100644 --- a/OptiGUI/src/main/java/opekope2/optigui/mixin/MinecraftMixin.java +++ b/OptiGUI/src/main/java/opekope2/optigui/mixin/MinecraftMixin.java @@ -8,6 +8,7 @@ import opekope2.optigui.interaction.GeneralInteraction; import opekope2.optigui.interaction.InteractionManager; import opekope2.optigui.interaction.InteractionTarget; +import opekope2.optigui.internal.AbstractOptiGuiClient; import opekope2.optigui.internal.ui.ResourceLoadingLogScreen; import opekope2.optigui.screen_api.screen.ITextureChangeableScreen; import org.jspecify.annotations.Nullable; @@ -47,6 +48,8 @@ private void manageInteraction(CallbackInfo ci) { @Inject(method = "addInitialScreens", at = @At("TAIL")) private void showResourceLoadingErrors(List> list, CallbackInfo ci) { + if (!AbstractOptiGuiClient.getImplementation().isInitialized()) + return; // Otherwise NeoForge will get stuck on "Loading Minecraft" screen if (ResourceLoadingLogScreen.shouldShow()) list.add(ResourceLoadingLogScreen::new); } diff --git a/OptiGUI/src/main/kotlin/opekope2/optigui/config/InspectorNbtDumper.kt b/OptiGUI/src/main/kotlin/opekope2/optigui/config/InspectorNbtDumper.kt index 586b49c14..09e96c230 100644 --- a/OptiGUI/src/main/kotlin/opekope2/optigui/config/InspectorNbtDumper.kt +++ b/OptiGUI/src/main/kotlin/opekope2/optigui/config/InspectorNbtDumper.kt @@ -1,10 +1,10 @@ package opekope2.optigui.config import com.mojang.datafixers.util.Pair -import dev.runefox.json.JsonArray import dev.runefox.json.JsonNode -import dev.runefox.json.JsonObject -import dev.runefox.json.JsonString +import dev.runefox.json.kt.JsonArray +import dev.runefox.json.kt.JsonObject +import dev.runefox.json.kt.JsonString import net.minecraft.nbt.NbtOps import net.minecraft.nbt.Tag import net.minecraft.network.chat.Style diff --git a/OptiGUI/src/main/kotlin/opekope2/optigui/filter/IFilterLoader.kt b/OptiGUI/src/main/kotlin/opekope2/optigui/filter/IFilterLoader.kt index 5870e1adc..a940b8be3 100644 --- a/OptiGUI/src/main/kotlin/opekope2/optigui/filter/IFilterLoader.kt +++ b/OptiGUI/src/main/kotlin/opekope2/optigui/filter/IFilterLoader.kt @@ -14,7 +14,7 @@ import org.slf4j.event.LoggingEvent import org.slf4j.spi.LoggingEventBuilder /** - * A filter supplier that loads [filters][TextureChangerFilter] from resources. + * A filter loader that loads [filters][TextureChangerFilter] from resources. */ interface IFilterLoader : PreparableReloadListener { /** @@ -33,7 +33,9 @@ interface IFilterLoader : PreparableReloadListener { val filters: Multimap /** - * Filter supplier registry. + * Filter loader registry. + * Filter loaders must also be registered using loader-specific APIs and with the same ID as registered here. + * On NeoForge, filter loaders must be registered to the loader above the `LOWEST` priority. */ companion object Registry : RegistryBase() { @JvmStatic diff --git a/OptiGUI/src/main/kotlin/opekope2/optigui/filter/transformer/NbtCompoundValuesTransformer.kt b/OptiGUI/src/main/kotlin/opekope2/optigui/filter/transformer/NbtCompoundValuesTransformer.kt index 33b8f7b22..921efda43 100644 --- a/OptiGUI/src/main/kotlin/opekope2/optigui/filter/transformer/NbtCompoundValuesTransformer.kt +++ b/OptiGUI/src/main/kotlin/opekope2/optigui/filter/transformer/NbtCompoundValuesTransformer.kt @@ -10,7 +10,7 @@ import net.minecraft.nbt.Tag * @see CompoundTag.get */ data object NbtCompoundValuesTransformer : INbtTransformer { - override fun transform(nbt: Tag, root: Tag) = + override fun transform(nbt: Tag, root: Tag): ListTag? = if (nbt !is CompoundTag) null else ListTag().apply { // Collection::mapTo uses MutableCollection::add diff --git a/OptiGUI/src/main/kotlin/opekope2/optigui/internal/AbstractOptiGuiClient.kt b/OptiGUI/src/main/kotlin/opekope2/optigui/internal/AbstractOptiGuiClient.kt index c3dcbbf48..d8b7eab97 100644 --- a/OptiGUI/src/main/kotlin/opekope2/optigui/internal/AbstractOptiGuiClient.kt +++ b/OptiGUI/src/main/kotlin/opekope2/optigui/internal/AbstractOptiGuiClient.kt @@ -1,6 +1,7 @@ package opekope2.optigui.internal import io.wispforest.owo.config.ui.ConfigScreenProviders +import opekope2.optigui.config.config import opekope2.optigui.filter.* import opekope2.optigui.filter.comparer.INbtComparer.ComparisonResult.* import opekope2.optigui.filter.comparer.NbtStringOrNumberComparer @@ -22,6 +23,12 @@ import org.jetbrains.annotations.MustBeInvokedByOverriders abstract class AbstractOptiGuiClient { abstract val version: String + abstract val isInitialized: Boolean + + // Available after initialize() + protected lateinit var filterLoaders: List + private set + abstract fun isModInstalled(modId: String): Boolean protected fun initialize() { @@ -35,11 +42,12 @@ abstract class AbstractOptiGuiClient { registerPrefixNbtFilters() registerNbtFilters() registerLoadTimeNbtProviders() - registerFilterLoaders() + filterLoaders = listOf(JsonFilterLoader) } @MustBeInvokedByOverriders protected open fun registerConfig() { + config.initialize() ConfigScreenProviders.register(MOD_ID, ::ConfigScreen) } @@ -131,11 +139,6 @@ abstract class AbstractOptiGuiClient { ILoadTimeNbtProvider.register("prefix_filters", PrefixNbtFilterNamesNbtProvider) } - @MustBeInvokedByOverriders - protected open fun registerFilterLoaders() { - JsonFilterLoader.initialize() - } - @ApiStatus.Internal companion object { @JvmStatic diff --git a/OptiGUI/src/main/kotlin/opekope2/optigui/internal/inspector/Inspector.kt b/OptiGUI/src/main/kotlin/opekope2/optigui/internal/inspector/Inspector.kt index e7b2b7fcb..530917c34 100644 --- a/OptiGUI/src/main/kotlin/opekope2/optigui/internal/inspector/Inspector.kt +++ b/OptiGUI/src/main/kotlin/opekope2/optigui/internal/inspector/Inspector.kt @@ -1,7 +1,7 @@ package opekope2.optigui.internal.inspector import dev.runefox.json.JsonNode -import dev.runefox.json.JsonObject +import dev.runefox.json.kt.JsonObject import opekope2.optigui.config.config import opekope2.optigui.interaction.IInteraction import opekope2.optigui.interaction.InteractionTarget @@ -11,7 +11,7 @@ import opekope2.optigui.util.JSON_RESOURCE_SCHEMA_URL internal object Inspector { fun generateJsonResource(interaction: IInteraction) = JsonObject { - it["\$schema"] = JSON_RESOURCE_SCHEMA_URL + it[$$"$schema"] = JSON_RESOURCE_SCHEMA_URL it.addTarget(interaction.target) it[JsonFilterResource.TEXTURE_CHANGERS_KEY] = getLastRenderedTextures() it[JsonFilterResource.SPRITE_CHANGERS_KEY] = getLastRenderedSprites() diff --git a/OptiGUI/src/main/kotlin/opekope2/optigui/internal/inspector/JsonInspectorOps.kt b/OptiGUI/src/main/kotlin/opekope2/optigui/internal/inspector/JsonInspectorOps.kt index fedaed17f..843bf008d 100644 --- a/OptiGUI/src/main/kotlin/opekope2/optigui/internal/inspector/JsonInspectorOps.kt +++ b/OptiGUI/src/main/kotlin/opekope2/optigui/internal/inspector/JsonInspectorOps.kt @@ -4,9 +4,10 @@ import com.mojang.datafixers.util.Pair import com.mojang.serialization.DataResult import com.mojang.serialization.RecordBuilder import dev.runefox.json.JsonNode -import dev.runefox.json.JsonNumber -import dev.runefox.json.JsonObject -import dev.runefox.json.copy +import dev.runefox.json.kt.ExperimentalKotlinJsonNodeApi +import dev.runefox.json.kt.JsonNumber +import dev.runefox.json.kt.JsonObject +import dev.runefox.json.kt.copy import net.minecraft.nbt.Tag import net.minecraft.resources.DelegatingOps import opekope2.optigui.filter.transformer.NbtTypeTransformer @@ -77,6 +78,7 @@ internal class JsonInspectorOps private constructor(private val withType: Boolea override fun initBuilder() = create(Tag.TAG_COMPOUND) + @OptIn(ExperimentalKotlinJsonNodeApi::class) override fun build(builder: JsonNode, prefix: JsonNode): DataResult = when { prefix.isNull -> success(builder) prefix.isObject -> success(prefix.copy { builder.forEachEntry(it::set) }) diff --git a/OptiGUI/src/main/kotlin/opekope2/optigui/internal/ui/ConfigScreen.kt b/OptiGUI/src/main/kotlin/opekope2/optigui/internal/ui/ConfigScreen.kt index eb2f88b2a..924e28a4b 100644 --- a/OptiGUI/src/main/kotlin/opekope2/optigui/internal/ui/ConfigScreen.kt +++ b/OptiGUI/src/main/kotlin/opekope2/optigui/internal/ui/ConfigScreen.kt @@ -13,9 +13,11 @@ import opekope2.optigui.util.MOD_ID import opekope2.optigui.util.expandTemplate import opekope2.optigui.util.mc import opekope2.optigui.util.requireChildById +import org.jetbrains.annotations.ApiStatus import io.wispforest.owo.config.ui.ConfigScreen as OwoConfigScreen -internal class ConfigScreen(parent: Screen?) : OwoConfigScreen(MODEL_ID, config, parent) { +@ApiStatus.Internal +class ConfigScreen(parent: Screen?) : OwoConfigScreen(MODEL_ID, config, parent) { private val rootComponent get() = uiAdapter.rootComponent private val toolsButton by requireChildById(::rootComponent) private var contextMenu: DropdownComponent? = null diff --git a/OptiGUI/src/main/kotlin/opekope2/optigui/util/dfu/Json5Ops.kt b/OptiGUI/src/main/kotlin/opekope2/optigui/util/dfu/Json5Ops.kt index d57d88143..c1c748a55 100644 --- a/OptiGUI/src/main/kotlin/opekope2/optigui/util/dfu/Json5Ops.kt +++ b/OptiGUI/src/main/kotlin/opekope2/optigui/util/dfu/Json5Ops.kt @@ -2,7 +2,8 @@ package opekope2.optigui.util.dfu import com.mojang.datafixers.util.Pair import com.mojang.serialization.* -import dev.runefox.json.* +import dev.runefox.json.JsonNode +import dev.runefox.json.kt.* import java.util.* import java.util.Spliterator.* import java.util.function.BiConsumer @@ -14,6 +15,7 @@ import java.util.stream.StreamSupport /** * JSON 5 pps for [JsonNode]. */ +@OptIn(ExperimentalKotlinJsonNodeApi::class) object Json5Ops : DynamicOps { private fun JsonNode?.takeIfNotNull() = this?.takeIf { !isNull } diff --git a/ScreenAPI-NeoForge/LICENSE b/ScreenAPI-NeoForge/LICENSE new file mode 120000 index 000000000..0c4b2ecec --- /dev/null +++ b/ScreenAPI-NeoForge/LICENSE @@ -0,0 +1 @@ +../ScreenAPI/LICENSE \ No newline at end of file diff --git a/ScreenAPI-NeoForge/build.gradle.kts b/ScreenAPI-NeoForge/build.gradle.kts new file mode 100644 index 000000000..ab598967a --- /dev/null +++ b/ScreenAPI-NeoForge/build.gradle.kts @@ -0,0 +1,25 @@ +import opekope2.optigui.buildscript.extension.Version +import opekope2.optigui.buildscript.extension.commonProject + +plugins { + id("opekope2.optigui.buildscript.plugin.Loader") + alias(libs.plugins.moddev) +} + +version = Version.neoForge(libs.versions.optigui, libs.versions.minecraft) + +base { + archivesName = "screen-api-neoforge" +} + +dependencies { + commonProject(":ScreenAPI") +} + +neoForge { + version = libs.versions.neoforge.get() + parchment { + minecraftVersion = libs.versions.minecraft + mappingsVersion = libs.versions.parchment + } +} diff --git a/ScreenAPI-NeoForge/src/main/resources/META-INF/neoforge.mods.toml b/ScreenAPI-NeoForge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 000000000..c50fbfb00 --- /dev/null +++ b/ScreenAPI-NeoForge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,29 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +issueTrackerURL = "https://github.com/opekope2/OptiGUI/issues" +license = "LGPL-3.0-or-later" + +[[mods]] +modId = "optigui_screen_api" +version = "$version" +displayName = "OptiGUI Screen API" +description = ''' +OptiGUI Screen API allows developers to add OptiGUI compatibility to GUI screens. +OptiGUI Screen API makes relevant vanilla GUI screens compatible with OptiGUI. +''' +logoFile = "icon.png" +logoBlur = false +features = { java_version = "[$java,)" } +authors = "opekope2" +displayURL = "https://opekope2.dev/OptiGUI" + +[[mixins]] +config = "optigui-screen-api.mixins.json" + +[[dependencies.optigui_screen_api]] +modId = "neoforge" +versionRange = "[$neoforge,)" + +[[dependencies.optigui_screen_api]] +modId = "minecraft" +versionRange = "[$minecraft,)" diff --git a/ScreenNBT-NeoForge/build.gradle.kts b/ScreenNBT-NeoForge/build.gradle.kts new file mode 100644 index 000000000..546569ddf --- /dev/null +++ b/ScreenNBT-NeoForge/build.gradle.kts @@ -0,0 +1,25 @@ +import opekope2.optigui.buildscript.extension.Version +import opekope2.optigui.buildscript.extension.commonProject + +plugins { + id("opekope2.optigui.buildscript.plugin.Loader") + alias(libs.plugins.moddev) +} + +version = Version.neoForge(libs.versions.optigui, libs.versions.minecraft) + +base { + archivesName = "screen-nbt-neoforge" +} + +dependencies { + commonProject(":ScreenNBT") +} + +neoForge { + version = libs.versions.neoforge.get() + parchment { + minecraftVersion = libs.versions.minecraft + mappingsVersion = libs.versions.parchment + } +} diff --git a/ScreenNBT-NeoForge/src/main/java/opekope2/optigui/screen_nbt/internal/neoforge/OptiGuiScreenNbtClient.java b/ScreenNBT-NeoForge/src/main/java/opekope2/optigui/screen_nbt/internal/neoforge/OptiGuiScreenNbtClient.java new file mode 100644 index 000000000..12acf8ae7 --- /dev/null +++ b/ScreenNBT-NeoForge/src/main/java/opekope2/optigui/screen_nbt/internal/neoforge/OptiGuiScreenNbtClient.java @@ -0,0 +1,15 @@ +package opekope2.optigui.screen_nbt.internal.neoforge; + +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.common.Mod; +import opekope2.optigui.interaction.nbt_provider.IInteractionNbtProvider; +import opekope2.optigui.screen_nbt.nbt_provider.ScreenNbtProvider; + +@Mod(value = OptiGuiScreenNbtClient.MOD_ID, dist = Dist.CLIENT) +public class OptiGuiScreenNbtClient { + public static final String MOD_ID = "optigui_screen_nbt"; + + public OptiGuiScreenNbtClient() { + IInteractionNbtProvider.Registry.register("screen", new ScreenNbtProvider()); + } +} diff --git a/ScreenNBT-NeoForge/src/main/java/opekope2/optigui/screen_nbt/internal/neoforge/package-info.java b/ScreenNBT-NeoForge/src/main/java/opekope2/optigui/screen_nbt/internal/neoforge/package-info.java new file mode 100644 index 000000000..9b73ef334 --- /dev/null +++ b/ScreenNBT-NeoForge/src/main/java/opekope2/optigui/screen_nbt/internal/neoforge/package-info.java @@ -0,0 +1,6 @@ +@ApiStatus.Internal +@NullMarked +package opekope2.optigui.screen_nbt.internal.neoforge; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/ScreenNBT-NeoForge/src/main/resources/META-INF/neoforge.mods.toml b/ScreenNBT-NeoForge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 000000000..6d1025dad --- /dev/null +++ b/ScreenNBT-NeoForge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,28 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +issueTrackerURL = "https://github.com/opekope2/OptiGUI/issues" +license = "LGPL-3.0-or-later" + +[[mods]] +modId = "optigui_screen_nbt" +version = "$version" +displayName = "OptiGUI Screen NBT" +description = ''' +OptiGUI Screen NBT mod provides GUI screen information as NBT, which can be filtered with JSON resources. +''' +logoFile = "icon.png" +logoBlur = false +features = { java_version = "[$java,)" } +authors = "opekope2" +displayURL = "https://opekope2.dev/OptiGUI" + +[[mixins]] +config = "optigui-screen-nbt.mixins.json" + +[[dependencies.optigui_screen_nbt]] +modId = "neoforge" +versionRange = "[$neoforge,)" + +[[dependencies.optigui_screen_nbt]] +modId = "minecraft" +versionRange = "[$minecraft,)" diff --git a/build.gradle.kts b/build.gradle.kts index b435b5418..5c5620673 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -44,7 +45,7 @@ subprojects { filter { includeGroup("dev.runefox") } } exclusiveContent { - forRepository { maven("https://maven.su5ed.dev/releases") { name = "Sinytra" } } + forRepository { maven("https://maven.sinytra.org") { name = "Sinytra" } } filter { includeGroup("org.sinytra"); includeGroup("org.sinytra.forgified-fabric-api") } } exclusiveContent { @@ -74,8 +75,9 @@ subprojects { withType().configureEach { compilerOptions { + jvmDefault = JvmDefaultMode.NO_COMPATIBILITY jvmTarget = libs.versions.java.map(JvmTarget::fromTarget) - freeCompilerArgs.addAll("-Xjvm-default=all", "-Xjsr305=strict") + freeCompilerArgs.add("-Xjsr305=strict") } } @@ -89,14 +91,18 @@ subprojects { "fabric_loader" to libs.versions.fabric.loader.get(), "fabric_api" to libs.versions.fabric.api.get(), "fabric_language_kotlin" to libs.versions.fabric.language.kotlin.get(), + "neoforge" to libs.versions.neoforge.get(), + "kotlin_for_forge" to libs.versions.kotlinforforge.get(), "minecraft" to libs.versions.minecraft.get(), "java" to libs.versions.java.get(), - "owo_lib" to libs.versions.owo.lib.fabric.get(), + "owo_fabric" to libs.versions.owo.lib.fabric.get(), + "owo_neoforge" to libs.versions.owo.lib.neoforge.get(), ) val commentRegex = """^\s*//.*$""".toRegex() inputs.properties(properties) filesMatching("fabric.mod.json") { expand(properties) } + filesMatching("META-INF/neoforge.mods.toml") { expand(properties) } filesMatching("*.mixins.json") { expand(properties) } filesMatching("assets/optigui/lang/*.jsonc") { name = name.removeSuffix("c") diff --git a/buildSrc/src/main/kotlin/opekope2/optigui/buildscript/task/GenerateWorldNbtProvider.kt b/buildSrc/src/main/kotlin/opekope2/optigui/buildscript/task/GenerateWorldNbtProvider.kt index baf8022a8..3c3daa2d9 100644 --- a/buildSrc/src/main/kotlin/opekope2/optigui/buildscript/task/GenerateWorldNbtProvider.kt +++ b/buildSrc/src/main/kotlin/opekope2/optigui/buildscript/task/GenerateWorldNbtProvider.kt @@ -88,7 +88,7 @@ abstract class GenerateWorldNbtProvider : AbstractCodegenTask() { classLoader.loadClass("net.minecraft.world.level.Level"), levelExcludedMethods ) - "getLevelData", - "net.minecraft.client.multiplayer.ClientLevel\$ClientLevelData" to levelDataExcludedMethods + getMethodNames( + $$"net.minecraft.client.multiplayer.ClientLevel$ClientLevelData" to levelDataExcludedMethods + getMethodNames( classLoader.loadClass("net.minecraft.world.level.storage.LevelData"), levelDataExcludedMethods ), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6628170f1..07773a2e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,35 +1,41 @@ [versions] optigui = "3.0.0-alpha.4" java = "21" -kotlin = "2.1.20" +kotlin = "2.3.0" dokka = "2.1.0" -loom = "1.14.0-alpha.25" -moddev = "2.0.116" -neoform = "1.21-20240613.152323" -minecraft = "1.21" -parchment = "2024.11.10" +loom = "1.14-SNAPSHOT" +moddev = "2.0.139" +neoform = "1.21.1-20240808.144430" +minecraft = "1.21.1" +parchment = "2024.11.17" mixin = "0.8.5" mixinextras = "0.5.0" fabric-loader = "0.16.10" -fabric-api = "0.102.0+1.21" -fabric-language-kotlin = "1.13.2+kotlin.2.1.20" -kotlinforforge = "5.7.0" +fabric-api = "0.102.0+1.21.1" +fabric-language-kotlin = "1.13.8+kotlin.2.3.0" +neoforge = "21.1.217" +kotlinforforge = "5.11.0" ini4j = "0.5.4" jspecify = "1.0.0" owo-lib-fabric = "0.12.15.1+1.21" owo-lib-neoforge = "0.12.15.1-beta.1+1.21" -runefox-json = "0.8" +runefox-json = "0.8.1" [libraries] kotlin-jvm-gradle-plugin = { group = "org.jetbrains.kotlin.jvm", name = "org.jetbrains.kotlin.jvm.gradle.plugin", version.ref = "kotlin" } dokka-gradle-plugin = { group = "org.jetbrains.dokka", name = "dokka-gradle-plugin", version.ref = "dokka" } +kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } +kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlin" } minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" } -parchment = { group = "org.parchmentmc.data", name = "parchment-1.21", version.ref = "parchment" } +parchment = { group = "org.parchmentmc.data", name = "parchment-1.21.1", version.ref = "parchment" } mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" } mixinextras-common = { group = "io.github.llamalad7", name = "mixinextras-common", version.ref = "mixinextras" } fabric-loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader" } fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } fabric-language-kotlin = { group = "net.fabricmc", name = "fabric-language-kotlin", version.ref = "fabric-language-kotlin" } +kfflang = { group = "thedarkcolour", name = "kfflang-neoforge", version.ref = "kotlinforforge" } +kfflib = { group = "thedarkcolour", name = "kfflib-neoforge", version.ref = "kotlinforforge" } +kffmod = { group = "thedarkcolour", name = "kffmod-neoforge", version.ref = "kotlinforforge" } ini4j = { group = "org.ini4j", name = "ini4j", version.ref = "ini4j" } jspecify = { group = "org.jspecify", name = "jspecify", version.ref = "jspecify" } owo-lib-fabric = { group = "io.wispforest", name = "owo-lib", version.ref = "owo-lib-fabric" } diff --git a/settings.gradle.kts b/settings.gradle.kts index c772d04ad..e4c616c0a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,8 +21,11 @@ dependencyResolutionManagement { include( "OptiGUI", "OptiGUI-Fabric", + "OptiGUI-NeoForge", "ScreenAPI", "ScreenAPI-Fabric", + "ScreenAPI-NeoForge", "ScreenNBT", "ScreenNBT-Fabric", + "ScreenNBT-NeoForge", )