Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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() {
Expand All @@ -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))
}
}
Expand Down
4 changes: 2 additions & 2 deletions OptiGUI-Fabric/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "*",
Expand Down
100 changes: 100 additions & 0 deletions OptiGUI-NeoForge/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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<String>("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("<v", NbtVersionFilter.Type.VERSION_LESS)
INbtFilter.register("~v", NbtVersionFilter.Type.VERSION_SAME_TO_NEXT_MINOR)
INbtFilter.register("^v", NbtVersionFilter.Type.VERSION_SAME_TO_NEXT_MAJOR)
}

override fun registerLoadTimeNbtProviders() {
super.registerLoadTimeNbtProviders()
ILoadTimeNbtProvider.register("mods", NeoForgeModsNbtProvider(this))
}

fun registerFilterLoaders(event: RegisterClientReloadListenersEvent) {
for (loader in filterLoaders) event.registerReloadListener(loader)
}

fun registerTextureChangerFilterLoader(event: RegisterClientReloadListenersEvent) {
event.registerReloadListener(TextureChanger)
}

@SubscribeEvent
fun afterWorldTick(event: LevelTickEvent.Post) {
if (!InteractionManager.isInteracting) return
InteractionManager.clearCache()
}

@SubscribeEvent
fun onDisconnect(event: ClientPlayerNetworkEvent.LoggingOut) {
InteractionManager.end(disconnected = true)
}

@SubscribeEvent
fun afterScreenInit(event: ScreenEvent.Init.Post) {
if (event.screen is ITextureChangeableScreen && config.enableInspector()) {
event.addListener(NeoForgeInspectorWidget())
}
}

@SubscribeEvent
fun beforeScreenRender(event: ScreenEvent.Render.Pre) {
TextureChanger.renderingScreen = true
}

@SubscribeEvent
fun afterScreenRender(event: ScreenEvent.Render.Post) {
TextureChanger.renderingScreen = false
}

override fun createScreen(container: ModContainer, modListScreen: Screen) = ConfigScreen(modListScreen)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package opekope2.optigui.internal.neoforge.dfu

import com.electronwill.nightconfig.core.Config
import com.electronwill.nightconfig.core.NullObject
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.JavaOps
import it.unimi.dsi.fastutil.bytes.ByteList
import it.unimi.dsi.fastutil.ints.IntList
import it.unimi.dsi.fastutil.longs.LongList
import it.unimi.dsi.fastutil.objects.Object2LongArrayMap
import it.unimi.dsi.fastutil.objects.Object2LongMap
import net.minecraft.resources.DelegatingOps
import java.time.temporal.ChronoField
import java.time.temporal.Temporal
import java.time.temporal.TemporalAccessor

internal object NightConfigOps : DelegatingOps<Any>(JavaOps.INSTANCE) {
override fun <U : Any> convertTo(outOps: DynamicOps<U>, 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<String> =
ChronoField.entries.fold(Object2LongArrayMap(ChronoField.entries.size)) { acc, field ->
acc[field.toString()] = getLong(field)
acc
}
}
Original file line number Diff line number Diff line change
@@ -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)
)
}
}
Original file line number Diff line number Diff line change
@@ -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))
}
}
Loading