diff options
145 files changed, 3378 insertions, 1894 deletions
@@ -4,24 +4,31 @@ SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> SPDX-License-Identifier: CC0-1.0 --> + + +<div align="center"> + # Firmament -<small><i>Powered by NEU</i></small> + + +<hr> [](https://hypixel.net/threads/firmament-a-skyblock-mod-for-1-20-1.5446366/) [](https://discord.gg/64pFP94AWA) [](https://modrinth.com/mod/firmament) [](https://github.com/nea89o/firmament/releases) +</div> + + ## Currently working features - Item List of all SkyBlock Items -- Grouping Items that belong together like minions - Recipe Viewer for Crafting Recipes - Recipe Viewer for Forge Recipes - ... as well as many more custom recipe types. - NPC waypoints -- Image Preview in chat - A storage overview as well as a full storage overlay - A crafting overlay when clicking the "Move Item" plus in a crafting recipe - Cursor position saver @@ -38,11 +45,15 @@ SPDX-License-Identifier: CC0-1.0 Firmament needs the following libraries to work: +- [Fabric API](https://modrinth.com/mod/fabric-api) +- [Fabric Language Kotlin](https://modrinth.com/mod/fabric-language-kotlin) + +As well as (for the item list): +- - [RoughlyEnoughItems](https://modrinth.com/mod/rei) - [Architectury](https://modrinth.com/mod/architectury-api) - [Cloth Config](https://modrinth.com/mod/cloth-config) -- [Fabric API](https://modrinth.com/mod/fabric-api) -- [Fabric Language Kotlin](https://modrinth.com/mod/fabric-language-kotlin) + You can download Firmament itself on [Modrinth](https://modrinth.com/mod/firmament) or on [GitHub](https://github.com/romangraef/firmament/releases). diff --git a/build.gradle.kts b/build.gradle.kts index d3d62b5..377cf07 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ */ import com.google.common.hash.Hashing -import com.google.devtools.ksp.gradle.KspTaskJvm +import com.google.devtools.ksp.gradle.KspAATask import com.google.gson.Gson import com.google.gson.JsonObject import moe.nea.licenseextractificator.LicenseDiscoveryTask @@ -15,10 +15,9 @@ import moe.nea.mcautotranslations.gradle.CollectTranslations import net.fabricmc.loom.LoomGradleExtension import org.apache.tools.ant.taskdefs.condition.Os import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.plugin.SubpluginOption import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.nio.charset.StandardCharsets -import java.util.Base64 +import java.util.* plugins { java @@ -29,10 +28,10 @@ plugins { alias(libs.plugins.kotlin.plugin.ksp) // alias(libs.plugins.loom) // TODO: use arch loom once they update to 1.8 - id("fabric-loom") version "1.9.2" + id("fabric-loom") version "1.10.1" alias(libs.plugins.shadow) id("moe.nea.licenseextractificator") - id("moe.nea.mc-auto-translations") version "0.2.0" + alias(libs.plugins.mcAutoTranslations) } version = getGitTagInfo(libs.versions.minecraft.get()) @@ -139,8 +138,10 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl val mainSS = sourceSets.main.get() val upperName = ss.name.capitalizeN() afterEvaluate { - tasks.named("ksp${upperName}Kotlin", KspTaskJvm::class) { - this.options.add(SubpluginOption("apoption", "firmament.sourceset=${ss.name}")) + tasks.named("ksp${upperName}Kotlin", KspAATask::class) { + this.commandLineArgumentProviders.add { // TODO: update https://github.com/google/ksp/issues/2075 + listOf("firmament.sourceset=${ss.name}") + } } tasks.named("compile${upperName}Kotlin", KotlinCompile::class) { this.enabled = isEnabled @@ -163,14 +164,16 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl extendsFrom(getByName(mainSS.annotationProcessorConfigurationName)) } (mainSS.runtimeOnlyConfigurationName) { - extendsFrom(getByName(ss.runtimeClasspathConfigurationName)) + if (isEnabled) + extendsFrom(getByName(ss.runtimeClasspathConfigurationName)) } ("ksp$upperName") { extendsFrom(ksp.get()) } } dependencies { - runtimeOnly(ss.output) + if (isEnabled) + runtimeOnly(ss.output) (ss.implementationConfigurationName)(project.files(tasks.compileKotlin.map { it.destinationDirectory })) (ss.implementationConfigurationName)(project.files(tasks.compileJava.map { it.destinationDirectory })) } @@ -192,6 +195,11 @@ val SourceSet.modImplementationConfigurationName loom.remapConfigurations.find { it.targetConfigurationName.get() == this.implementationConfigurationName }!!.sourceConfiguration +val SourceSet.modRuntimeOnlyConfigurationName + get() = + loom.remapConfigurations.find { + it.targetConfigurationName.get() == this.runtimeOnlyConfigurationName + }!!.sourceConfiguration val shadowMe by configurations.creating { exclude(group = "org.jetbrains.kotlin") @@ -219,8 +227,10 @@ val testAgent by configurations.creating { } -val configuredSourceSet = createIsolatedSourceSet("configured", - isEnabled = false) // Wait for update (also low prio, because configured sucks) +val configuredSourceSet = createIsolatedSourceSet( + "configured", + isEnabled = false +) // Wait for update (also low prio, because configured sucks) val sodiumSourceSet = createIsolatedSourceSet("sodium", isEnabled = false) val citResewnSourceSet = createIsolatedSourceSet("citresewn", isEnabled = false) // TODO: Wait for update val yaclSourceSet = createIsolatedSourceSet("yacl") @@ -254,14 +264,14 @@ dependencies { include(libs.hypixelmodapi.fabric) compileOnly(projects.javaplugin) annotationProcessor(projects.javaplugin) - implementation("com.google.auto.service:auto-service-annotations:1.1.1") + nonModImplentation("com.google.auto.service:auto-service-annotations:1.1.1") ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0") include(libs.manninghamMills) include(libs.moulconfig) annotationProcessor(libs.mixinextras) - implementation(libs.mixinextras) + nonModImplentation(libs.mixinextras) include(libs.mixinextras) nonModImplentation(libs.nealisp) @@ -269,7 +279,6 @@ dependencies { modCompileOnly(libs.fabric.api) modRuntimeOnly(libs.fabric.api.deprecated) - modApi(libs.architectury) modCompileOnly(libs.jarvis.api) include(libs.jarvis.fabric) @@ -286,10 +295,8 @@ dependencies { (yaclSourceSet.modImplementationConfigurationName)(libs.yacl) // Actual dependencies - (reiSourceSet.modImplementationConfigurationName)(libs.rei.api) { - exclude(module = "architectury") - exclude(module = "architectury-fabric") - } + (reiSourceSet.modImplementationConfigurationName)(libs.rei.api) + (reiSourceSet.modRuntimeOnlyConfigurationName)(libs.rei.fabric) nonModImplentation(libs.repoparser) shadowMe(libs.repoparser) fun ktor(mod: String) = "io.ktor:ktor-$mod-jvm:${libs.versions.ktor.get()}" @@ -312,8 +319,8 @@ dependencies { } - testImplementation("io.kotest:kotest-runner-junit5:6.0.0.M1") - testAgent(project(":testagent", configuration = "shadow")) + testImplementation("net.fabricmc:fabric-loader-junit:${libs.versions.fabric.loader.get()}") + testAgent(files(tasks.getByPath(":testagent:jar"))) implementation(projects.symbols) ksp(projects.symbols) @@ -327,11 +334,13 @@ loom { configureEach { property("fabric.log.level", "info") property("firmament.debug", "true") - property("firmament.classroots", - compatSourceSets.joinToString(File.pathSeparator) { - File(it.output.classesDirs.asPath).absolutePath - }) + property( + "firmament.classroots", + compatSourceSets.joinToString(File.pathSeparator) { + File(it.output.classesDirs.asPath).absolutePath + }) property("mixin.debug.export", "true") + property("mixin.debug", "true") parseEnvFile(file(".env")).forEach { (t, u) -> environmentVariable(t, u) @@ -364,12 +373,16 @@ val updateTestRepo by tasks.registering { doLast { val propertiesFile = rootProject.file("gradle.properties") val json = - Gson().fromJson(uri("https://api.github.com/repos/NotEnoughUpdates/NotEnoughUpdates-REPO/branches/master") - .toURL().readText(), JsonObject::class.java) + Gson().fromJson( + uri("https://api.github.com/repos/NotEnoughUpdates/NotEnoughUpdates-REPO/branches/master") + .toURL().readText(), JsonObject::class.java + ) val latestSha = json["commit"].asJsonObject["sha"].asString var text = propertiesFile.readText() - text = text.replace("firmament\\.compiletimerepohash=[^\n]*".toRegex(), - "firmament.compiletimerepohash=$latestSha") + text = text.replace( + "firmament\\.compiletimerepohash=[^\n]*".toRegex(), + "firmament.compiletimerepohash=$latestSha" + ) propertiesFile.writeText(text) } } @@ -383,8 +396,10 @@ tasks.test { doFirst { wd.mkdirs() wd.resolve("config").deleteRecursively() - systemProperty("firmament.testrepo", - downloadTestRepo.flatMap { it.outputDirectory.asFile }.map { it.absolutePath }.get()) + systemProperty( + "firmament.testrepo", + downloadTestRepo.flatMap { it.outputDirectory.asFile }.map { it.absolutePath }.get() + ) jvmArgs("-javaagent:${testAgent.singleFile.absolutePath}") } systemProperty("jdk.attach.allowAttachSelf", "true") @@ -402,13 +417,15 @@ tasks.withType<JavaCompile> { this.targetCompatibility = "21" options.encoding = "UTF-8" val module = "ALL-UNNAMED" - options.forkOptions.jvmArgs!!.addAll(listOf( - "--add-exports=jdk.compiler/com.sun.tools.javac.util=$module", - "--add-exports=jdk.compiler/com.sun.tools.javac.comp=$module", - "--add-exports=jdk.compiler/com.sun.tools.javac.tree=$module", - "--add-exports=jdk.compiler/com.sun.tools.javac.api=$module", - "--add-exports=jdk.compiler/com.sun.tools.javac.code=$module", - )) + options.forkOptions.jvmArgs!!.addAll( + listOf( + "--add-exports=jdk.compiler/com.sun.tools.javac.util=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.comp=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.api=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.code=$module", + ) + ) options.isFork = true afterEvaluate { options.compilerArgs.add("-Xplugin:IntermediaryNameReplacement mappingFile=${LoomGradleExtension.get(project).mappingsFile.absolutePath} sourceNs=named") @@ -457,12 +474,18 @@ tasks.processResources { tasks.scanLicenses { scanConfiguration(nonModImplentation) scanConfiguration(configurations.modCompileClasspath.get()) + compatSourceSets.forEach { + scanConfiguration(it.modImplementationConfigurationName.get()) + } outputFile.set(layout.buildDirectory.file("LICENSES-FIRMAMENT.json")) licenseFormatter.set(moe.nea.licenseextractificator.JsonLicenseFormatter()) } -tasks.create("printAllLicenses", LicenseDiscoveryTask::class.java, licensing).apply { +tasks.register("printAllLicenses", LicenseDiscoveryTask::class.java, licensing).configure { outputFile.set(layout.buildDirectory.file("LICENSES-FIRMAMENT.txt")) licenseFormatter.set(moe.nea.licenseextractificator.TextLicenseFormatter()) + compatSourceSets.forEach { + scanConfiguration(it.modImplementationConfigurationName.get()) + } scanConfiguration(nonModImplentation) scanConfiguration(configurations.modCompileClasspath.get()) doLast { @@ -499,16 +522,20 @@ fun patchRenderDoc( if (!fileF.exists()) { fileF.parentFile.mkdirs() if (isWindows) { - fileF.writeText(""" + fileF.writeText( + """ setlocal enableextensions start "" renderdoccmd.exe capture --opt-hook-children --wait-for-exit --working-dir . "$wrappedJavaExecutable" %* endlocal - """.trimIndent()) + """.trimIndent() + ) } else { - fileF.writeText(""" + fileF.writeText( + """ #!/usr/bin/env bash exec renderdoccmd capture --opt-hook-children --wait-for-exit --working-dir . "$wrappedJavaExecutable" "$@" - """.trimIndent()) + """.trimIndent() + ) fileF.setExecutable(true) } } diff --git a/docs/firmament_logo.webp b/docs/firmament_logo.webp Binary files differnew file mode 100644 index 0000000..d70327a --- /dev/null +++ b/docs/firmament_logo.webp diff --git a/docs/firmament_logo.webp.license b/docs/firmament_logo.webp.license new file mode 100644 index 0000000..8b77b1b --- /dev/null +++ b/docs/firmament_logo.webp.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 ic22487 + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/firmament_logo_256.webp b/docs/firmament_logo_256.webp Binary files differnew file mode 100644 index 0000000..2aba841 --- /dev/null +++ b/docs/firmament_logo_256.webp diff --git a/docs/firmament_logo_256.webp.license b/docs/firmament_logo_256.webp.license new file mode 100644 index 0000000..8b77b1b --- /dev/null +++ b/docs/firmament_logo_256.webp.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 ic22487 + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/firmament_logo_256_nobg.webp b/docs/firmament_logo_256_nobg.webp Binary files differnew file mode 100644 index 0000000..c557fca --- /dev/null +++ b/docs/firmament_logo_256_nobg.webp diff --git a/docs/firmament_logo_256_nobg.webp.license b/docs/firmament_logo_256_nobg.webp.license new file mode 100644 index 0000000..8b77b1b --- /dev/null +++ b/docs/firmament_logo_256_nobg.webp.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 ic22487 + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/firmament_logo_nobg.webp b/docs/firmament_logo_nobg.webp Binary files differnew file mode 100644 index 0000000..9b76f3c --- /dev/null +++ b/docs/firmament_logo_nobg.webp diff --git a/docs/firmament_logo_nobg.webp.license b/docs/firmament_logo_nobg.webp.license new file mode 100644 index 0000000..8b77b1b --- /dev/null +++ b/docs/firmament_logo_nobg.webp.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2025 ic22487 + +SPDX-License-Identifier: CC-BY-4.0 diff --git a/docs/release_script.sh b/docs/release_script.sh index 43663b4..8d87a09 100755 --- a/docs/release_script.sh +++ b/docs/release_script.sh @@ -4,6 +4,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # ARG_OPTIONAL_BOOLEAN([no-check],[n],[Skip checking preconditions, such as a clean git working directory]) +# ARG_OPTIONAL_BOOLEAN([no-test],[t],[Skip running gradle tests.]) +# ARG_OPTIONAL_BOOLEAN([dry],[d],[Dry run]) # ARG_HELP([Script to help creating releases]) # ARGBASH_GO() # needed because of Argbash --> m4_ignore([ @@ -23,20 +25,24 @@ die() begins_with_short_option() { - local first_option all_short_options='nh' + local first_option all_short_options='ntdh' first_option="${1:0:1}" test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0 } # THE DEFAULTS INITIALIZATION - OPTIONALS _arg_no_check="off" +_arg_no_test="off" +_arg_dry="off" print_help() { printf '%s\n' "Script to help creating releases" - printf 'Usage: %s [-n|--(no-)no-check] [-h|--help]\n' "$0" + printf 'Usage: %s [-n|--(no-)no-check] [-t|--(no-)no-test] [-d|--(no-)dry] [-h|--help]\n' "$0" printf '\t%s\n' "-n, --no-check, --no-no-check: Skip checking preconditions, such as a clean git working directory (off by default)" + printf '\t%s\n' "-t, --no-test, --no-no-test: Skip running gradle tests. (off by default)" + printf '\t%s\n' "-d, --dry, --no-dry: Dry run (off by default)" printf '\t%s\n' "-h, --help: Prints help" } @@ -59,6 +65,30 @@ parse_commandline() { begins_with_short_option "$_next" && shift && set -- "-n" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option." fi ;; + -t|--no-no-test|--no-test) + _arg_no_test="on" + test "${1:0:5}" = "--no-" && _arg_no_test="off" + ;; + -t*) + _arg_no_test="on" + _next="${_key##-t}" + if test -n "$_next" -a "$_next" != "$_key" + then + { begins_with_short_option "$_next" && shift && set -- "-t" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option." + fi + ;; + -d|--no-dry|--dry) + _arg_dry="on" + test "${1:0:5}" = "--no-" && _arg_dry="off" + ;; + -d*) + _arg_dry="on" + _next="${_key##-d}" + if test -n "$_next" -a "$_next" != "$_key" + then + { begins_with_short_option "$_next" && shift && set -- "-d" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option." + fi + ;; -h|--help) print_help exit 0 @@ -129,19 +159,28 @@ fi echo "Confirming new version as $newversion" -echo Committing release commit -git commit --allow-empty -m 'Prepare release '"$newversion"' +if [ "$_arg_dry" == off ]; then + echo Committing release commit + git commit --allow-empty -m 'Prepare release '"$newversion"' [no changelog]' -echo Tagging release commit -git tag "$newversion" + echo Tagging release commit + git tag "$newversion" +fi mkdir -p "$basedir/.gradle" releasenotes="$basedir/.gradle/releasenotes.md" +comparetag="$( +if [ "$_arg_dry" == off ]; then + echo "$newversion" +else + echo "HEAD" +fi)" + echo Building release notes -echo "**Full Changelog**: <https://github.com/nea89o/Firmament/compare/$oldversion...$newversion>" > "$releasenotes" +echo "**Full Changelog**: <https://github.com/nea89o/Firmament/compare/$oldversion...$comparetag>" > "$releasenotes" echo >> "$releasenotes" -git log --pretty='- %s' --grep '[no changelog]' --invert-grep --fixed-strings "$oldversion..$newversion" | tac >> "$releasenotes" +git log --pretty='- %s' --grep '[no changelog]' --invert-grep --fixed-strings "$oldversion..$comparetag" | tac >> "$releasenotes" echo >> "$releasenotes" echo Check Release notes: @@ -153,22 +192,29 @@ read echo Building JAR "$basedir"/gradlew --stop -"$basedir"/gradlew clean build +if [ "$_arg_no_test" == off ]; then + echo Building and testing + "$basedir"/gradlew clean build +else + echo Building without testing + "$basedir"/gradlew clean assemble +fi echo Release notes: echo ---------------------------------------------- cat "$releasenotes" echo ---------------------------------------------- -echo Pushing to github -git push "$REMOTE" "HEAD" "$newversion" - -if command -v gh; then - echo Creating github release - (set -x; gh release create -t "Firmament $newversion" "$newversion" -F "$releasenotes" "$basedir/build/libs/Firmament-$newversion.jar") -else - echo Could not find github command utility. Opening github releases - xdg-open "https://github.com/nea89o/firmament/releases/new" +if [ "$_arg_dry" == off ]; then + echo Pushing to github + git push "$REMOTE" "HEAD" "$newversion" + if command -v gh; then + echo Creating github release + (set -x; gh release create -t "Firmament $newversion" "$newversion" -F "$releasenotes" "$basedir/build/libs/Firmament-$newversion.jar") + else + echo Could not find github command utility. Opening github releases + xdg-open "https://github.com/nea89o/firmament/releases/new" + fi fi echo Opening modrinth releases diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dc1f36d..0750b11 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,23 +3,24 @@ # SPDX-License-Identifier: CC0-1.0 [versions] -minecraft = "1.21.4" +minecraft = "1.21.5" # Update from https://kotlinlang.org/ -kotlin = "2.1.10" +kotlin = "2.1.20" # Update from https://github.com/google/ksp/releases -kotlin_ksp = "2.1.10-1.0.30" +kotlin_ksp = "2.1.20-2.0.0" # Update from https://linkie.shedaniel.me/dependencies?loader=fabric -fabric_loader = "0.16.10" -fabric_api = "0.117.0+1.21.4" -yarn = "1.21.4+build.8" -modmenu = "13.0.2" -architectury = "15.0.1" -rei = "18.0.796" +fabric_loader = "0.16.13" +fabric_api = "0.119.9+1.21.5" +yarn = "1.21.5+build.1" +modmenu = "14.0.0-rc.2" +architectury = "16.0.3" +# Update from https://maven.architectury.dev/me/shedaniel/RoughlyEnoughItems-fabric/ (but is typically late) +rei = "19.0.805" # Update from https://maven.fabricmc.net/net/fabricmc/fabric-language-kotlin/ -fabric_kotlin = "1.13.1+kotlin.2.1.10" +fabric_kotlin = "1.13.2+kotlin.2.1.20" # Update from https://maven.architectury.dev/dev/architectury/loom/dev.architectury.loom.gradle.plugin/ loom = "1.7.414" # TODO: port back to architectury (and) 1.9.424 @@ -28,33 +29,33 @@ loom = "1.7.414" # TODO: port back to architectury (and) 1.9.424 qolify = "1.6.0-1.21.1" # Update from https://modrinth.com/mod/sodium/versions?l=fabric -sodium = "mc1.21.4-0.6.6-fabric" +sodium = "mc1.21.5-0.6.13-fabric" # Update from https://modrinth.com/mod/freecam/versions?l=fabric -freecammod = "1.3.2+mc1.21.4" +freecammod = "1.3.3+mc1.21.5" # Update from https://modrinth.com/mod/no-chat-reports/versions?l=fabric -ncr = "Fabric-1.21.4-v2.11.0" +ncr = "Fabric-1.21.5-v2.12.0" # Update from https://modrinth.com/mod/female-gender/versions?l=fabric -femalegender = "4.3.3+1.21.4" +femalegender = "4.3.4+1.21.5" # Update from https://modrinth.com/mod/explosive-enhancement/versions?l=fabric explosiveenhancement = "1.2.3-1.21.0" # Update from https://modrinth.com/mod/not-enough-animations/versions?l=fabric -notenoughanimations = "eZykTicT" +notenoughanimations = "prj4BdjU" # Update from https://modrinth.com/mod/cit-resewn/versions?l=fabric citresewn = "1.2.0+1.21" # Update from https://modrinth.com/mod/jade/versions?l=fabric -jade = "17.2.2+fabric" +jade = "18.1.0+fabric" devauth = "1.2.1" -# Update from https://ktor.io/ -ktor = "3.0.3" +# Update from https://ktor.io/docs/ +ktor = "3.1.2" # Update from https://repo.nea.moe/#/releases/moe/nea/neurepoparser neurepoparser = "1.7.0" @@ -70,12 +71,15 @@ jarvis = "1.1.4" nealisp = "1.1.0" # Update from https://github.com/NotEnoughUpdates/MoulConfig/tags -moulconfig = "3.3.0" +moulconfig = "3.8.0" + +# Update from https://repo.nea.moe/#/releases/moe/nea/mc-auto-translations/moe.nea.mc-auto-translations.gradle.plugin +mcAutoTranslations = "0.3.0" # Update from https://www.curseforge.com/minecraft/mc-mods/configured/files/all?page=1&pageSize=20 configured = "6023970" -# Update from https://modrinth.com/mod/hypixel-mod-api/versions +# Update from https://modrinth.com/mod/hypixel-mod-api/versions?l=fabric hypixelmodapi = "1.0.1" hypixelmodapi_fabric = "1.0.1+build.1+mc1.21" @@ -84,16 +88,16 @@ manninghamMills = "2.4.1" # Update from https://docs.isxander.dev/yet-another-config-lib/installing-yacl # Nvm, they just don't update docs: https://modrinth.com/mod/yacl/versions?l=fabric -yacl = "3.6.2+1.21.4-fabric" +yacl = "3.6.6+1.21.5-fabric" -# Update from https://maven.shedaniel.me/me/shedaniel/cloth/basic-math/0.6.1/ +# Update from https://maven.shedaniel.me/me/shedaniel/cloth/basic-math/ basicMath = "0.6.1" # Update from https://mvnrepository.com/artifact/net.lenni0451.classtransform/core -classtransform = "1.14.0" +classtransform = "1.14.1" # Update from https://mvnrepository.com/artifact/org.ow2.asm/asm/ -asm = "9.7.1" +asm = "9.8" [libraries] minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } @@ -103,7 +107,7 @@ fabric_api_deprecated = { module = "net.fabricmc.fabric-api:fabric-api-deprecate fabric_kotlin = { module = "net.fabricmc:fabric-language-kotlin", version.ref = "fabric_kotlin" } architectury = { module = "dev.architectury:architectury", version.ref = "architectury" } rei_api = { module = "me.shedaniel:RoughlyEnoughItems-api", version.ref = "rei" } -moulconfig = { module = "org.notenoughupdates.moulconfig:modern", version.ref = "moulconfig" } +moulconfig = { module = "org.notenoughupdates.moulconfig:modern-1.21.5", version.ref = "moulconfig" } repoparser = { module = "moe.nea:neurepoparser", version.ref = "neurepoparser" } mixinextras = { module = "io.github.llamalad7:mixinextras-fabric", version.ref = "mixinextras" } jarvis_api = { module = "moe.nea.jarvis:jarvis-api", version.ref = "jarvis" } @@ -139,7 +143,7 @@ asm = { module = "org.ow2.asm:asm", version.ref = "asm" } [bundles] runtime_required = [ - "rei_fabric", + # "rei_fabric", # "notenoughanimations", "hypixelmodapi_fabric", ] @@ -147,8 +151,8 @@ runtime_optional = [ "devauth", # "freecammod", # "sodium", - # "qolify", - "ncr", + # "qolify", + # "ncr", # "citresewn", ] @@ -159,3 +163,4 @@ kotlin_plugin_powerassert = { id = "org.jetbrains.kotlin.plugin.power-assert", v kotlin_plugin_ksp = { id = "com.google.devtools.ksp", version.ref = "kotlin_ksp" } loom = { id = "dev.architectury.loom", version.ref = "loom" } shadow = { id = "com.github.johnrengelman.shadow", version = "8.1.1" } +mcAutoTranslations = { id = "moe.nea.mc-auto-translations", version.ref = "mcAutoTranslations" } diff --git a/src/compat/moulconfig/java/ProcessedOptionFirm.kt b/src/compat/moulconfig/java/ProcessedOptionFirm.kt index 4d0096c..6936048 100644 --- a/src/compat/moulconfig/java/ProcessedOptionFirm.kt +++ b/src/compat/moulconfig/java/ProcessedOptionFirm.kt @@ -10,6 +10,9 @@ abstract class ProcessedOptionFirm( private val accordionId: Int, private val config: Config ) : ProcessedOption { + override fun getPath(): String? { + return "nonsense" + } lateinit var category: ProcessedCategoryFirm override fun getAccordionId(): Int { return accordionId diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt index aade59e..b5c9a6d 100644 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt +++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/FirmamentReiPlugin.kt @@ -1,5 +1,6 @@ package moe.nea.firmament.compat.rei +import io.github.moulberry.repo.data.NEUCraftingRecipe import me.shedaniel.rei.api.client.plugins.REIClientPlugin import me.shedaniel.rei.api.client.registry.category.CategoryRegistry import me.shedaniel.rei.api.client.registry.display.DisplayRegistry @@ -19,11 +20,10 @@ import net.minecraft.item.ItemStack import net.minecraft.text.Text import net.minecraft.util.ActionResult import net.minecraft.util.Identifier -import moe.nea.firmament.compat.rei.recipes.SBCraftingRecipe -import moe.nea.firmament.compat.rei.recipes.SBEssenceUpgradeRecipe -import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe +import moe.nea.firmament.compat.rei.recipes.GenericREIRecipeCategory import moe.nea.firmament.compat.rei.recipes.SBKatRecipe import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe +import moe.nea.firmament.compat.rei.recipes.SBRecipe import moe.nea.firmament.compat.rei.recipes.SBReforgeRecipe import moe.nea.firmament.compat.rei.recipes.SBShopRecipe import moe.nea.firmament.events.HandledScreenPushREIEvent @@ -31,6 +31,9 @@ import moe.nea.firmament.features.inventory.CraftingOverlay import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.repo.recipes.SBCraftingRecipeRenderer +import moe.nea.firmament.repo.recipes.SBEssenceUpgradeRecipeRenderer +import moe.nea.firmament.repo.recipes.SBForgeRecipeRenderer import moe.nea.firmament.util.MC import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.guessRecipeId @@ -52,19 +55,23 @@ class FirmamentReiPlugin : REIClientPlugin { registry.register(TransferHandler { context -> val screen = context.containerScreen val display = context.display - if (display !is SBCraftingRecipe) return@TransferHandler TransferHandler.Result.createNotApplicable() - val neuItem = RepoManager.getNEUItem(SkyblockId(display.neuRecipe.output.itemId)) - ?: error("Could not find neu item ${display.neuRecipe.output.itemId} which is used in a recipe output") + if (display !is SBRecipe) return@TransferHandler TransferHandler.Result.createNotApplicable() + val recipe = display.neuRecipe + if (recipe !is NEUCraftingRecipe) return@TransferHandler TransferHandler.Result.createNotApplicable() + val neuItem = RepoManager.getNEUItem(SkyblockId(recipe.output.itemId)) + ?: error("Could not find neu item ${recipe.output.itemId} which is used in a recipe output") val useSuperCraft = context.isStackedCrafting || RepoManager.Config.alwaysSuperCraft if (neuItem.isVanilla && useSuperCraft) return@TransferHandler TransferHandler.Result.createFailed(Text.translatable( "firmament.recipe.novanilla")) var shouldReturn = true if (context.isActuallyCrafting && !useSuperCraft) { - if (screen !is GenericContainerScreen || screen.title?.unformattedString != CraftingOverlay.CRAFTING_SCREEN_NAME) { + val craftingScreen = (screen as? GenericContainerScreen) + ?.takeIf { it.title?.unformattedString == CraftingOverlay.CRAFTING_SCREEN_NAME } + if (craftingScreen == null) { MC.sendCommand("craft") shouldReturn = false } - CraftingOverlay.setOverlay(screen as? GenericContainerScreen, display.neuRecipe) + CraftingOverlay.setOverlay(craftingScreen, recipe) } if (context.isActuallyCrafting && useSuperCraft) { shouldReturn = false @@ -75,13 +82,17 @@ class FirmamentReiPlugin : REIClientPlugin { } + val generics = listOf<GenericREIRecipeCategory<*>>( // Order matters: The order in here is the order in which they show up in REI + GenericREIRecipeCategory(SBCraftingRecipeRenderer), + GenericREIRecipeCategory(SBForgeRecipeRenderer), + GenericREIRecipeCategory(SBEssenceUpgradeRecipeRenderer), + ) + override fun registerCategories(registry: CategoryRegistry) { - registry.add(SBCraftingRecipe.Category) - registry.add(SBForgeRecipe.Category) + registry.add(generics) registry.add(SBMobDropRecipe.Category) registry.add(SBKatRecipe.Category) registry.add(SBReforgeRecipe.Category) - registry.add(SBEssenceUpgradeRecipe.Category) registry.add(SBShopRecipe.Category) } @@ -91,17 +102,14 @@ class FirmamentReiPlugin : REIClientPlugin { } override fun registerDisplays(registry: DisplayRegistry) { - registry.registerDisplayGenerator( - SBCraftingRecipe.Category.catIdentifier, - SkyblockCraftingRecipeDynamicGenerator) + generics.forEach { + it.registerDynamicGenerator(registry) + } registry.registerDisplayGenerator( SBReforgeRecipe.catIdentifier, SBReforgeRecipe.DynamicGenerator ) registry.registerDisplayGenerator( - SBForgeRecipe.Category.categoryIdentifier, - SkyblockForgeRecipeDynamicGenerator) - registry.registerDisplayGenerator( SBMobDropRecipe.Category.categoryIdentifier, SkyblockMobDropRecipeDynamicGenerator) registry.registerDisplayGenerator( @@ -110,10 +118,6 @@ class FirmamentReiPlugin : REIClientPlugin { registry.registerDisplayGenerator( SBKatRecipe.Category.categoryIdentifier, SkyblockKatRecipeDynamicGenerator) - registry.registerDisplayGenerator( - SBEssenceUpgradeRecipe.Category.categoryIdentifier, - SkyblockEssenceRecipeDynamicGenerator - ) } override fun registerCollapsibleEntries(registry: CollapsibleEntryRegistry) { diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/REIRecipeLayouter.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/REIRecipeLayouter.kt new file mode 100644 index 0000000..8e39f28 --- /dev/null +++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/REIRecipeLayouter.kt @@ -0,0 +1,62 @@ +package moe.nea.firmament.compat.rei + +import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import me.shedaniel.math.Dimension +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.widgets.Widget +import me.shedaniel.rei.api.client.gui.widgets.Widgets +import net.minecraft.text.Text +import moe.nea.firmament.compat.rei.recipes.wrapWidget +import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.repo.recipes.RecipeLayouter + +class REIRecipeLayouter : RecipeLayouter { + val container: MutableList<Widget> = mutableListOf() + fun <T: Widget> add(t: T): T = t.also(container::add) + + override fun createItemSlot( + x: Int, + y: Int, + content: SBItemStack?, + slotKind: RecipeLayouter.SlotKind + ) { + val slot = Widgets.createSlot(Point(x, y)) + if (content != null) + slot.entry(SBItemEntryDefinition.getEntry(content)) + when (slotKind) { + RecipeLayouter.SlotKind.SMALL_INPUT -> slot.markInput() + RecipeLayouter.SlotKind.SMALL_OUTPUT -> slot.markOutput() + RecipeLayouter.SlotKind.BIG_OUTPUT -> { + slot.markOutput().disableBackground() + add(Widgets.createResultSlotBackground(Point(x, y))) + } + } + add(slot) + } + + override fun createTooltip(rectangle: Rectangle, label: Text) { + add(Widgets.createTooltip(rectangle, label)) + } + + override fun createLabel(x: Int, y: Int, text: Text) { + add(Widgets.createLabel(Point(x, y), text)) + } + + override fun createArrow(x: Int, y: Int) = + add(Widgets.createArrow(Point(x, y))).bounds + + override fun createMoulConfig( + x: Int, + y: Int, + w: Int, + h: Int, + component: GuiComponent + ) { + add(wrapWidget(Rectangle(Point(x, y), Dimension(w, h)), component)) + } + + override fun createFire(ingredientsCenter: Point, animationTicks: Int) { + add(Widgets.createBurningFire(ingredientsCenter).animationDurationTicks(animationTicks.toDouble())) + } +} diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt index e80840f..900ebab 100644 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt +++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockCraftingRecipeDynamicGenerator.kt @@ -1,6 +1,5 @@ package moe.nea.firmament.compat.rei -import io.github.moulberry.repo.data.NEUCraftingRecipe import io.github.moulberry.repo.data.NEUForgeRecipe import io.github.moulberry.repo.data.NEUKatUpgradeRecipe import io.github.moulberry.repo.data.NEUMobDropRecipe @@ -11,9 +10,6 @@ import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator import me.shedaniel.rei.api.client.view.ViewSearchBuilder import me.shedaniel.rei.api.common.display.Display import me.shedaniel.rei.api.common.entry.EntryStack -import moe.nea.firmament.compat.rei.recipes.SBCraftingRecipe -import moe.nea.firmament.compat.rei.recipes.SBEssenceUpgradeRecipe -import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe import moe.nea.firmament.compat.rei.recipes.SBKatRecipe import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe import moe.nea.firmament.compat.rei.recipes.SBShopRecipe @@ -22,33 +18,27 @@ import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.repo.SBItemStack -val SkyblockCraftingRecipeDynamicGenerator = - neuDisplayGenerator<SBCraftingRecipe, NEUCraftingRecipe> { SBCraftingRecipe(it) } - -val SkyblockForgeRecipeDynamicGenerator = - neuDisplayGenerator<SBForgeRecipe, NEUForgeRecipe> { SBForgeRecipe(it) } - val SkyblockMobDropRecipeDynamicGenerator = neuDisplayGenerator<SBMobDropRecipe, NEUMobDropRecipe> { SBMobDropRecipe(it) } val SkyblockShopRecipeDynamicGenerator = neuDisplayGenerator<SBShopRecipe, NEUNpcShopRecipe> { SBShopRecipe(it) } val SkyblockKatRecipeDynamicGenerator = neuDisplayGenerator<SBKatRecipe, NEUKatUpgradeRecipe> { SBKatRecipe(it) } -val SkyblockEssenceRecipeDynamicGenerator = - neuDisplayGeneratorWithItem<SBEssenceUpgradeRecipe, EssenceRecipeProvider.EssenceUpgradeRecipe> { item, recipe -> - SBEssenceUpgradeRecipe(recipe, item) - } inline fun <D : Display, reified T : NEURecipe> neuDisplayGenerator(crossinline mapper: (T) -> D) = neuDisplayGeneratorWithItem<D, T> { _, it -> mapper(it) } inline fun <D : Display, reified T : NEURecipe> neuDisplayGeneratorWithItem(crossinline mapper: (SBItemStack, T) -> D) = + neuDisplayGeneratorWithItem(T::class.java, mapper) +inline fun <D : Display, T : NEURecipe> neuDisplayGeneratorWithItem( + filter: Class<T>, + crossinline mapper: (SBItemStack, T) -> D) = object : DynamicDisplayGenerator<D> { override fun getRecipeFor(entry: EntryStack<*>): Optional<List<D>> { if (entry.type != SBItemEntryDefinition.type) return Optional.empty() val item = entry.castValue<SBItemStack>() val recipes = RepoManager.getRecipesFor(item.skyblockId) - val craftingRecipes = recipes.filterIsInstance<T>() + val craftingRecipes = recipes.filterIsInstance<T>(filter) return Optional.of(craftingRecipes.map { mapper(item, it) }) } @@ -60,7 +50,7 @@ inline fun <D : Display, reified T : NEURecipe> neuDisplayGeneratorWithItem(cros if (entry.type != SBItemEntryDefinition.type) return Optional.empty() val item = entry.castValue<SBItemStack>() val recipes = RepoManager.getUsagesFor(item.skyblockId) - val craftingRecipes = recipes.filterIsInstance<T>() + val craftingRecipes = recipes.filterIsInstance<T>(filter) return Optional.of(craftingRecipes.map { mapper(item, it) }) } } diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt index 518f7b4..9ccfab4 100644 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt +++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/SkyblockItemIdFocusedStackProvider.kt @@ -9,7 +9,6 @@ import me.shedaniel.rei.api.common.entry.EntryStack import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.screen.ingame.HandledScreen import moe.nea.firmament.mixins.accessor.AccessorHandledScreen -import moe.nea.firmament.util.skyBlockId object SkyblockItemIdFocusedStackProvider : FocusedStackProvider { override fun provide(screen: Screen?, mouse: Point?): CompoundEventResult<EntryStack<*>> { diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/GenericREIRecipeCategory.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/GenericREIRecipeCategory.kt new file mode 100644 index 0000000..15cb818 --- /dev/null +++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/GenericREIRecipeCategory.kt @@ -0,0 +1,67 @@ +package moe.nea.firmament.compat.rei.recipes + +import io.github.moulberry.repo.data.NEURecipe +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.gui.Renderer +import me.shedaniel.rei.api.client.gui.widgets.Widget +import me.shedaniel.rei.api.client.gui.widgets.Widgets +import me.shedaniel.rei.api.client.registry.display.DisplayCategory +import me.shedaniel.rei.api.client.registry.display.DisplayRegistry +import me.shedaniel.rei.api.common.category.CategoryIdentifier +import me.shedaniel.rei.api.common.util.EntryStacks +import net.minecraft.text.Text +import moe.nea.firmament.compat.rei.REIRecipeLayouter +import moe.nea.firmament.compat.rei.neuDisplayGeneratorWithItem +import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.repo.recipes.GenericRecipeRenderer + +class GenericREIRecipeCategory<T : NEURecipe>( + val renderer: GenericRecipeRenderer<T>, +) : DisplayCategory<GenericRecipe<T>> { + private val dynamicGenerator = + neuDisplayGeneratorWithItem<GenericRecipe<T>, T>(renderer.typ) { item, recipe -> + GenericRecipe( + recipe, + item, + categoryIdentifier + ) + } + + private val categoryIdentifier = CategoryIdentifier.of<GenericRecipe<T>>(renderer.identifier) + override fun getCategoryIdentifier(): CategoryIdentifier<GenericRecipe<T>> { + return categoryIdentifier + } + + override fun getDisplayHeight(): Int { + return renderer.displayHeight + } + + override fun getTitle(): Text? { + return renderer.title + } + + override fun getIcon(): Renderer? { + return EntryStacks.of(renderer.icon) + } + + override fun setupDisplay(display: GenericRecipe<T>, bounds: Rectangle): List<Widget> { + val layouter = REIRecipeLayouter() + layouter.container.add(Widgets.createRecipeBase(bounds)) + renderer.render(display.neuRecipe, bounds, layouter, display.sourceItem) + return layouter.container + } + + fun registerDynamicGenerator(registry: DisplayRegistry) { + registry.registerDisplayGenerator(categoryIdentifier, dynamicGenerator) + } +} + +class GenericRecipe<T : NEURecipe>( + override val neuRecipe: T, + val sourceItem: SBItemStack?, + val id: CategoryIdentifier<GenericRecipe<T>> +) : SBRecipe() { + override fun getCategoryIdentifier(): CategoryIdentifier<*>? { + return id + } +} diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt deleted file mode 100644 index c02e078..0000000 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBCraftingRecipe.kt +++ /dev/null @@ -1,56 +0,0 @@ -package moe.nea.firmament.compat.rei.recipes - -import io.github.moulberry.repo.data.NEUCraftingRecipe -import io.github.moulberry.repo.data.NEUIngredient -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.gui.Renderer -import me.shedaniel.rei.api.client.gui.widgets.Widget -import me.shedaniel.rei.api.client.gui.widgets.Widgets -import me.shedaniel.rei.api.client.registry.display.DisplayCategory -import me.shedaniel.rei.api.common.category.CategoryIdentifier -import me.shedaniel.rei.api.common.display.Display -import me.shedaniel.rei.api.common.display.DisplaySerializer -import me.shedaniel.rei.api.common.util.EntryStacks -import net.minecraft.block.Blocks -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.compat.rei.SBItemEntryDefinition -import moe.nea.firmament.repo.SBItemStack - -class SBCraftingRecipe(override val neuRecipe: NEUCraftingRecipe) : SBRecipe() { - override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.catIdentifier - - object Category : DisplayCategory<SBCraftingRecipe> { - val catIdentifier = CategoryIdentifier.of<SBCraftingRecipe>(Firmament.MOD_ID, "crafting_recipe") - override fun getCategoryIdentifier(): CategoryIdentifier<out SBCraftingRecipe> = catIdentifier - - override fun getTitle(): Text = Text.literal("SkyBlock Crafting") - - override fun getIcon(): Renderer = SBItemEntryDefinition.getPassthrough(Blocks.CRAFTING_TABLE) - override fun setupDisplay(display: SBCraftingRecipe, bounds: Rectangle): List<Widget> { - val point = Point(bounds.centerX - 58, bounds.centerY - 27) - return buildList { - add(Widgets.createRecipeBase(bounds)) - add(Widgets.createArrow(Point(point.x + 60, point.y + 18))) - add(Widgets.createResultSlotBackground(Point(point.x + 95, point.y + 19))) - for (i in 0 until 3) { - for (j in 0 until 3) { - val slot = Widgets.createSlot(Point(point.x + 1 + i * 18, point.y + 1 + j * 18)).markInput() - add(slot) - val item = display.neuRecipe.inputs[i + j * 3] - if (item == NEUIngredient.SENTINEL_EMPTY) continue - slot.entry(SBItemEntryDefinition.getEntry(item)) - } - } - add( - Widgets.createSlot(Point(point.x + 95, point.y + 19)) - .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.output)) - .disableBackground().markOutput() - ) - } - } - - } - -} diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt deleted file mode 100644 index e0a3784..0000000 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBEssenceUpgradeRecipe.kt +++ /dev/null @@ -1,62 +0,0 @@ -package moe.nea.firmament.compat.rei.recipes - -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.gui.Renderer -import me.shedaniel.rei.api.client.gui.widgets.Widget -import me.shedaniel.rei.api.client.gui.widgets.Widgets -import me.shedaniel.rei.api.client.registry.display.DisplayCategory -import me.shedaniel.rei.api.common.category.CategoryIdentifier -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.compat.rei.SBItemEntryDefinition -import moe.nea.firmament.repo.EssenceRecipeProvider -import moe.nea.firmament.repo.SBItemStack -import moe.nea.firmament.util.SkyblockId - -class SBEssenceUpgradeRecipe(override val neuRecipe: EssenceRecipeProvider.EssenceUpgradeRecipe, - val sourceItem: SBItemStack) : SBRecipe() { - object Category : DisplayCategory<SBEssenceUpgradeRecipe> { - override fun getCategoryIdentifier(): CategoryIdentifier<SBEssenceUpgradeRecipe> = - CategoryIdentifier.of(Firmament.MOD_ID, "essence_upgrade") - - override fun getTitle(): Text { - return Text.literal("Essence Upgrades") - } - - override fun getIcon(): Renderer { - return SBItemEntryDefinition.getEntry(SkyblockId("ESSENCE_WITHER")) - } - - override fun setupDisplay(display: SBEssenceUpgradeRecipe, bounds: Rectangle): List<Widget> { - val recipe = display.neuRecipe - val list = mutableListOf<Widget>() - list.add(Widgets.createRecipeBase(bounds)) - list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 - 18 / 2)) - .markInput() - .entry(SBItemEntryDefinition.getEntry(display.sourceItem.copy(stars = recipe.starCountAfter - 1)))) - list.add(Widgets.createSlot(Point(bounds.minX + 12, bounds.centerY - 8 + 18 / 2)) - .markInput() - .entry(SBItemEntryDefinition.getEntry(recipe.essenceIngredient))) - list.add(Widgets.createSlot(Point(bounds.maxX - 12 - 16, bounds.centerY - 8)) - .markOutput() - .entry(SBItemEntryDefinition.getEntry(display.sourceItem.copy(stars = recipe.starCountAfter)))) - val extraItems = recipe.extraItems - list.add(Widgets.createArrow(Point(bounds.centerX - 24 / 2, - if (extraItems.isEmpty()) bounds.centerY - 17 / 2 - else bounds.centerY + 18 / 2))) - for ((index, item) in extraItems.withIndex()) { - list.add(Widgets.createSlot( - Point(bounds.centerX - extraItems.size * 16 / 2 - 2 / 2 + index * 18, - bounds.centerY - 18 / 2)) - .markInput() - .entry(SBItemEntryDefinition.getEntry(item))) - } - return list - } - } - - override fun getCategoryIdentifier(): CategoryIdentifier<*> { - return Category.categoryIdentifier - } -} diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt deleted file mode 100644 index 7a0ec78..0000000 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBForgeRecipe.kt +++ /dev/null @@ -1,71 +0,0 @@ -package moe.nea.firmament.compat.rei.recipes - -import io.github.moulberry.repo.data.NEUForgeRecipe -import me.shedaniel.math.Point -import me.shedaniel.math.Rectangle -import me.shedaniel.rei.api.client.gui.Renderer -import me.shedaniel.rei.api.client.gui.widgets.Widget -import me.shedaniel.rei.api.client.gui.widgets.Widgets -import me.shedaniel.rei.api.client.registry.display.DisplayCategory -import me.shedaniel.rei.api.common.category.CategoryIdentifier -import kotlin.math.cos -import kotlin.math.sin -import kotlin.time.Duration.Companion.seconds -import net.minecraft.block.Blocks -import net.minecraft.text.Text -import moe.nea.firmament.Firmament -import moe.nea.firmament.compat.rei.SBItemEntryDefinition -import moe.nea.firmament.compat.rei.plus - -class SBForgeRecipe(override val neuRecipe: NEUForgeRecipe) : SBRecipe() { - override fun getCategoryIdentifier(): CategoryIdentifier<*> = Category.categoryIdentifier - - object Category : DisplayCategory<SBForgeRecipe> { - override fun getCategoryIdentifier(): CategoryIdentifier<SBForgeRecipe> = - CategoryIdentifier.of(Firmament.MOD_ID, "forge_recipe") - - override fun getTitle(): Text = Text.literal("Forge Recipes") - override fun getDisplayHeight(): Int { - return 104 - } - - override fun getIcon(): Renderer = SBItemEntryDefinition.getPassthrough(Blocks.ANVIL) - override fun setupDisplay(display: SBForgeRecipe, bounds: Rectangle): List<Widget> { - return buildList { - add(Widgets.createRecipeBase(bounds)) - add(Widgets.createResultSlotBackground(Point(bounds.minX + 124, bounds.minY + 46))) - val arrow = Widgets.createArrow(Point(bounds.minX + 90, bounds.minY + 54 - 18 / 2)) - add(arrow) - add(Widgets.createTooltip(arrow.bounds, - Text.stringifiedTranslatable("firmament.recipe.forge.time", - display.neuRecipe.duration.seconds))) - val ingredientsCenter = Point(bounds.minX + 49 - 8, bounds.minY + 54 - 8) - add(Widgets.createBurningFire(ingredientsCenter).animationDurationTicks(25.0)) - val count = display.neuRecipe.inputs.size - if (count == 1) { - add( - Widgets.createSlot(Point(ingredientsCenter.x, ingredientsCenter.y)).markInput() - .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.inputs.single())) - ) - } else { - display.neuRecipe.inputs.forEachIndexed { idx, ingredient -> - val rad = Math.PI * 2 * idx / count - add( - Widgets.createSlot( - Point( - cos(rad) * 30, - sin(rad) * 30, - ) + ingredientsCenter - ).markInput().entry(SBItemEntryDefinition.getEntry(ingredient)) - ) - } - } - add( - Widgets.createSlot(Point(bounds.minX + 124, bounds.minY + 46)).markOutput().disableBackground() - .entry(SBItemEntryDefinition.getEntry(display.neuRecipe.outputStack)) - ) - } - } - } - -} diff --git a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt index b8313a6..c5b4fb6 100644 --- a/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt +++ b/src/compat/rei/java/moe/nea/firmament/compat/rei/recipes/SBReforgeRecipe.kt @@ -19,6 +19,7 @@ import me.shedaniel.rei.api.common.entry.EntryIngredient import me.shedaniel.rei.api.common.entry.EntryStack import net.minecraft.entity.EntityType import net.minecraft.entity.SpawnReason +import net.minecraft.registry.entry.RegistryEntry import net.minecraft.text.Text import net.minecraft.util.Identifier import net.minecraft.village.VillagerProfession @@ -33,6 +34,7 @@ import moe.nea.firmament.repo.RepoManager import moe.nea.firmament.repo.SBItemStack import moe.nea.firmament.util.AprilFoolsUtil import moe.nea.firmament.util.FirmFormatters +import moe.nea.firmament.util.MC import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.gold import moe.nea.firmament.util.grey @@ -92,7 +94,7 @@ class SBReforgeRecipe( list.add(Widgets.withTooltip( EntityWidget( EntityType.VILLAGER.create(EntityRenderer.fakeWorld, SpawnReason.COMMAND) - ?.also { it.villagerData = it.villagerData.withProfession(VillagerProfession.WEAPONSMITH) }, + ?.also { it.villagerData = it.villagerData.withProfession(MC.currentOrDefaultRegistries.getEntryOrThrow(VillagerProfession.WEAPONSMITH)) }, Point(bounds.minX + 10 + 24 + 8 - dimension.width / 2, bounds.centerY - dimension.height / 2), dimension ), diff --git a/src/main/java/moe/nea/firmament/init/MixinPlugin.java b/src/main/java/moe/nea/firmament/init/MixinPlugin.java index 61e8f14..d48139b 100644 --- a/src/main/java/moe/nea/firmament/init/MixinPlugin.java +++ b/src/main/java/moe/nea/firmament/init/MixinPlugin.java @@ -8,54 +8,69 @@ import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class MixinPlugin implements IMixinConfigPlugin { - AutoDiscoveryPlugin autoDiscoveryPlugin = new AutoDiscoveryPlugin(); - public static String mixinPackage; - @Override - public void onLoad(String mixinPackage) { - MixinExtrasBootstrap.init(); - MixinPlugin.mixinPackage = mixinPackage; - autoDiscoveryPlugin.setMixinPackage(mixinPackage); - } - - @Override - public String getRefMapperConfig() { - return null; - } - - @Override - public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - if (!Boolean.getBoolean("firmament.debug") && mixinClassName.contains("devenv.")) { - return false; - } - return true; - } - - @Override - public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { - - } - - @Override - public List<String> getMixins() { - return autoDiscoveryPlugin.getMixins().stream().filter(it -> this.shouldApplyMixin(null, it)) - .toList(); - } - - @Override - public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - - } - - public static List<String> appliedMixins = new ArrayList<>(); - - @Override - public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - appliedMixins.add(mixinClassName); - } + AutoDiscoveryPlugin autoDiscoveryPlugin = new AutoDiscoveryPlugin(); + public static List<MixinPlugin> instances = new ArrayList<>(); + public String mixinPackage; + + @Override + public void onLoad(String mixinPackage) { + MixinExtrasBootstrap.init(); + instances.add(this); + this.mixinPackage = mixinPackage; + autoDiscoveryPlugin.setMixinPackage(mixinPackage); + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + if (!Boolean.getBoolean("firmament.debug") && mixinClassName.contains("devenv.")) { + return false; + } + return true; + } + + @Override + public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { + + } + + @Override + public List<String> getMixins() { + return autoDiscoveryPlugin.getMixins().stream().filter(it -> this.shouldApplyMixin(null, it)) + .toList(); + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } + + public Set<String> getAppliedFullPathMixins() { + return new HashSet<>(appliedMixins); + } + + public Set<String> getExpectedFullPathMixins() { + return getMixins() + .stream() + .map(it -> mixinPackage + "." + it) + .collect(Collectors.toSet()); + } + + public List<String> appliedMixins = new ArrayList<>(); + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + appliedMixins.add(mixinClassName); + } } diff --git a/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java b/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java index f2c6c53..8b65946 100644 --- a/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java +++ b/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java @@ -3,10 +3,9 @@ package moe.nea.firmament.init; import me.shedaniel.mm.api.ClassTinkerers; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.block.BlockState; -import net.minecraft.client.render.block.BlockModels; import net.minecraft.client.render.block.BlockRenderManager; import net.minecraft.client.render.chunk.SectionBuilder; -import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BlockStateModel; import net.minecraft.util.math.BlockPos; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -20,98 +19,100 @@ import org.objectweb.asm.tree.VarInsnNode; public class SectionBuilderRiser extends RiserUtils { - @IntermediaryName(SectionBuilder.class) - String SectionBuilder; - @IntermediaryName(BlockPos.class) - String BlockPos; - @IntermediaryName(BlockRenderManager.class) - String BlockRenderManager; - @IntermediaryName(BlockState.class) - String BlockState; - @IntermediaryName(BakedModel.class) - String BakedModel; - String CustomBlockTextures = "moe.nea.firmament.features.texturepack.CustomBlockTextures"; + @IntermediaryName(SectionBuilder.class) + String SectionBuilder; + @IntermediaryName(BlockPos.class) + String BlockPos; + @IntermediaryName(BlockRenderManager.class) + String BlockRenderManager; + @IntermediaryName(BlockState.class) + String BlockState; + @IntermediaryName(BlockStateModel.class) + String BlockStateModel; + String CustomBlockTextures = "moe.nea.firmament.features.texturepack.CustomBlockTextures"; - Type getModelDesc = Type.getMethodType( - getTypeForClassName(BlockRenderManager), - getTypeForClassName(BlockState) - ); - String getModel = remapper.mapMethodName( - "intermediary", - Intermediary.<BlockRenderManager>className(), - Intermediary.methodName(net.minecraft.client.render.block.BlockRenderManager::getModel), - Type.getMethodDescriptor( - getTypeForClassName(Intermediary.<BakedModel>className()), - getTypeForClassName(Intermediary.<BlockState>className()) - ) - ); + Type getModelDesc = Type.getMethodType( + getTypeForClassName(BlockRenderManager), + getTypeForClassName(BlockState) + ); + String getModel = remapper.mapMethodName( + "intermediary", + Intermediary.<BlockRenderManager>className(), + Intermediary.methodName(net.minecraft.client.render.block.BlockRenderManager::getModel), + Type.getMethodDescriptor( + getTypeForClassName(Intermediary.<BlockStateModel>className()), + getTypeForClassName(Intermediary.<BlockState>className()) + ) + ); - @Override - public void addTinkerers() { - if (FabricLoader.getInstance().isModLoaded("fabric-renderer-indigo")) - ClassTinkerers.addTransformation(SectionBuilder, this::handle, true); - } + @Override + public void addTinkerers() { + if (FabricLoader.getInstance().isModLoaded("fabric-renderer-indigo")) + ClassTinkerers.addTransformation(SectionBuilder, this::handle, true); + } - private void handle(ClassNode classNode) { - for (MethodNode method : classNode.methods) { - if ((method.name.endsWith("$fabric-renderer-indigo$hookBuildRenderBlock") - || method.name.endsWith("$fabric-renderer-indigo$hookChunkBuildTessellate")) && - method.name.startsWith("redirect$")) { - handleIndigo(method); - return; - } - } - System.err.println("Could not inject indigo rendering hook. Is a custom renderer installed (e.g. sodium)?"); - } + private void handle(ClassNode classNode) { + System.out.println("AVAST! "+ getModel); + for (MethodNode method : classNode.methods) { + if ((method.name.endsWith("$fabric-renderer-indigo$hookBuildRenderBlock") + || method.name.endsWith("$fabric-renderer-indigo$hookChunkBuildTessellate")) && + method.name.startsWith("redirect$")) { + handleIndigo(method); + return; + } + } + System.err.println("Could not inject indigo rendering hook. Is a custom renderer installed (e.g. sodium)?"); + } - private void handleIndigo(MethodNode method) { - LocalVariableNode blockPosVar = null, blockStateVar = null; - for (LocalVariableNode localVariable : method.localVariables) { - if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockPos))) { - blockPosVar = localVariable; - } - if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockState))) { - blockStateVar = localVariable; - } - } - if (blockPosVar == null || blockStateVar == null) { - System.err.println("Firmament could inject into indigo: missing either block pos or blockstate"); - return; - } - for (AbstractInsnNode instruction : method.instructions) { - if (instruction.getOpcode() != Opcodes.INVOKEVIRTUAL) continue; - var methodInsn = (MethodInsnNode) instruction; - if (!(methodInsn.name.equals(getModel) && Type.getObjectType(methodInsn.owner).equals(getTypeForClassName(BlockRenderManager)))) - continue; - method.instructions.insertBefore( - methodInsn, - new MethodInsnNode( - Opcodes.INVOKESTATIC, - getTypeForClassName(CustomBlockTextures).getInternalName(), - "enterFallbackCall", - Type.getMethodDescriptor(Type.VOID_TYPE) - )); + private void handleIndigo(MethodNode method) { + LocalVariableNode blockPosVar = null, blockStateVar = null; + for (LocalVariableNode localVariable : method.localVariables) { + if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockPos))) { + blockPosVar = localVariable; + } + if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockState))) { + blockStateVar = localVariable; + } + } + if (blockPosVar == null || blockStateVar == null) { + System.err.println("Firmament could inject into indigo: missing either block pos or blockstate"); + return; + } + for (AbstractInsnNode instruction : method.instructions) { + if (instruction.getOpcode() != Opcodes.INVOKEVIRTUAL) continue; + var methodInsn = (MethodInsnNode) instruction; + if (!(methodInsn.name.equals(getModel) && Type.getObjectType(methodInsn.owner).equals(getTypeForClassName(BlockRenderManager)))) + continue; + method.instructions.insertBefore( + methodInsn, + new MethodInsnNode( + Opcodes.INVOKESTATIC, + getTypeForClassName(CustomBlockTextures).getInternalName(), + "enterFallbackCall", + Type.getMethodDescriptor(Type.VOID_TYPE) + )); - var insnList = new InsnList(); - insnList.add(new MethodInsnNode( - Opcodes.INVOKESTATIC, - getTypeForClassName(CustomBlockTextures).getInternalName(), - "exitFallbackCall", - Type.getMethodDescriptor(Type.VOID_TYPE) - )); - insnList.add(new VarInsnNode(Opcodes.ALOAD, blockPosVar.index)); - insnList.add(new VarInsnNode(Opcodes.ALOAD, blockStateVar.index)); - insnList.add(new MethodInsnNode( - Opcodes.INVOKESTATIC, - getTypeForClassName(CustomBlockTextures).getInternalName(), - "patchIndigo", - Type.getMethodDescriptor(getTypeForClassName(BakedModel), - getTypeForClassName(BakedModel), - getTypeForClassName(BlockPos), - getTypeForClassName(BlockState)), - false - )); - method.instructions.insert(methodInsn, insnList); - } - } + var insnList = new InsnList(); + insnList.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, + getTypeForClassName(CustomBlockTextures).getInternalName(), + "exitFallbackCall", + Type.getMethodDescriptor(Type.VOID_TYPE) + )); + insnList.add(new VarInsnNode(Opcodes.ALOAD, blockPosVar.index)); + insnList.add(new VarInsnNode(Opcodes.ALOAD, blockStateVar.index)); + insnList.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, + getTypeForClassName(CustomBlockTextures).getInternalName(), + "patchIndigo", + Type.getMethodDescriptor( + getTypeForClassName(BlockStateModel), + getTypeForClassName(BlockStateModel), + getTypeForClassName(BlockPos), + getTypeForClassName(BlockState)), + false + )); + method.instructions.insert(methodInsn, insnList); + } + } } diff --git a/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java b/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java index b20c223..f07604e 100644 --- a/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java +++ b/src/main/java/moe/nea/firmament/mixins/PlayerDropEventPatch.java @@ -20,7 +20,7 @@ public abstract class PlayerDropEventPatch extends PlayerEntity { @Inject(method = "dropSelectedItem", at = @At("HEAD"), cancellable = true) public void onDropSelectedItem(boolean entireStack, CallbackInfoReturnable<Boolean> cir) { - Slot fakeSlot = new Slot(getInventory(), getInventory().selectedSlot, 0, 0); + Slot fakeSlot = new Slot(getInventory(), getInventory().getSelectedSlot(), 0, 0); if (IsSlotProtectedEvent.shouldBlockInteraction(fakeSlot, SlotActionType.THROW, IsSlotProtectedEvent.MoveOrigin.DROP_FROM_HOTBAR)) { cir.setReturnValue(false); } diff --git a/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java b/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java index 06ecbd4..a4ae931 100644 --- a/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java +++ b/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java @@ -43,11 +43,11 @@ public abstract class SlotUpdateListener extends ClientCommonNetworkHandler { private void onMultiSlotUpdate(InventoryS2CPacket packet, CallbackInfo ci) { var player = this.client.player; assert player != null; - if (packet.getSyncId() == 0) { - PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Multi(packet.getContents())); - } else if (packet.getSyncId() == player.currentScreenHandler.syncId) { + if (packet.syncId() == 0) { + PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Multi(packet.contents())); + } else if (packet.syncId() == player.currentScreenHandler.syncId) { ChestInventoryUpdateEvent.Companion.publish( - new ChestInventoryUpdateEvent.Multi(packet.getContents()) + new ChestInventoryUpdateEvent.Multi(packet.contents()) ); } } diff --git a/src/main/java/moe/nea/firmament/mixins/SoundReceiveEventPatch.java b/src/main/java/moe/nea/firmament/mixins/SoundReceiveEventPatch.java index 5c52d70..b8cba80 100644 --- a/src/main/java/moe/nea/firmament/mixins/SoundReceiveEventPatch.java +++ b/src/main/java/moe/nea/firmament/mixins/SoundReceiveEventPatch.java @@ -1,30 +1,32 @@ package moe.nea.firmament.mixins; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; import moe.nea.firmament.events.SoundReceiveEvent; import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ClientPlayNetworkHandler.class) public class SoundReceiveEventPatch { - @Inject(method = "onPlaySound", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientWorld;playSound(Lnet/minecraft/entity/player/PlayerEntity;DDDLnet/minecraft/registry/entry/RegistryEntry;Lnet/minecraft/sound/SoundCategory;FFJ)V"), cancellable = true) - private void postEventWhenSoundIsPlayed(PlaySoundS2CPacket packet, CallbackInfo ci) { + @WrapWithCondition(method = "onPlaySound", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/world/ClientWorld;playSound(Lnet/minecraft/entity/Entity;DDDLnet/minecraft/registry/entry/RegistryEntry;Lnet/minecraft/sound/SoundCategory;FFJ)V")) + private boolean postEventWhenSoundIsPlayed(ClientWorld instance, @Nullable Entity source, double x, double y, double z, RegistryEntry<SoundEvent> sound, SoundCategory category, float volume, float pitch, long seed) { var event = new SoundReceiveEvent( - packet.getSound(), - packet.getCategory(), - new Vec3d(packet.getX(), packet.getY(), packet.getZ()), - packet.getPitch(), - packet.getVolume(), - packet.getSeed() + sound, + category, + new Vec3d(x,y,z), + pitch, + volume, + seed ); SoundReceiveEvent.Companion.publish(event); - if (event.getCancelled()) { - ci.cancel(); - } + return !event.getCancelled(); } } diff --git a/src/main/java/moe/nea/firmament/mixins/WorldRenderLastEventPatch.java b/src/main/java/moe/nea/firmament/mixins/WorldRenderLastEventPatch.java index 847fb4d..3ed8c1b 100644 --- a/src/main/java/moe/nea/firmament/mixins/WorldRenderLastEventPatch.java +++ b/src/main/java/moe/nea/firmament/mixins/WorldRenderLastEventPatch.java @@ -2,11 +2,9 @@ package moe.nea.firmament.mixins; -import com.llamalad7.mixinextras.sugar.Local; import moe.nea.firmament.events.WorldRenderLastEvent; import net.minecraft.client.render.*; import net.minecraft.client.util.Handle; -import net.minecraft.client.util.ObjectAllocator; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.profiler.Profiler; import org.joml.Matrix4f; @@ -31,12 +29,12 @@ public abstract class WorldRenderLastEventPatch { protected abstract void checkEmpty(MatrixStack matrices); @Inject(method = "method_62214", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiler/Profiler;pop()V", shift = At.Shift.AFTER)) - public void onWorldRenderLast(Fog fog, RenderTickCounter tickCounter, Camera camera, Profiler profiler, Matrix4f matrix4f, Matrix4f matrix4f2, Handle handle, Handle handle2, Handle handle3, Handle handle4, boolean bl, Frustum frustum, Handle handle5, CallbackInfo ci) { + public void onWorldRenderLast(Fog fog, RenderTickCounter renderTickCounter, Camera camera, Profiler profiler, Matrix4f matrix4f, Matrix4f matrix4f2, Handle handle, Handle handle2, boolean bl, Frustum frustum, Handle handle3, Handle handle4, CallbackInfo ci) { var imm = this.bufferBuilders.getEntityVertexConsumers(); var stack = new MatrixStack(); // TODO: pre-cancel this event if F1 is active var event = new WorldRenderLastEvent( - stack, tickCounter, + stack, renderTickCounter, camera, imm ); diff --git a/src/main/java/moe/nea/firmament/mixins/feature/DisableSlotHighlights.java b/src/main/java/moe/nea/firmament/mixins/feature/DisableSlotHighlights.java new file mode 100644 index 0000000..0abed22 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/feature/DisableSlotHighlights.java @@ -0,0 +1,25 @@ +package moe.nea.firmament.mixins.feature; + +import moe.nea.firmament.features.fixes.Fixes; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Slot.class) +public abstract class DisableSlotHighlights { + @Shadow + public abstract ItemStack getStack(); + + @Inject(method = "canBeHighlighted", at = @At("HEAD"), cancellable = true) + private void dontHighlight(CallbackInfoReturnable<Boolean> cir) { + if (!Fixes.TConfig.INSTANCE.getHideSlotHighlights()) return; + var display = getStack().get(DataComponentTypes.TOOLTIP_DISPLAY); + if (display != null && display.hideTooltip()) + cir.setReturnValue(false); + } +} diff --git a/src/main/kotlin/Firmament.kt b/src/main/kotlin/Firmament.kt index 79f9743..7bc7d44 100644 --- a/src/main/kotlin/Firmament.kt +++ b/src/main/kotlin/Firmament.kt @@ -26,6 +26,7 @@ import net.fabricmc.loader.api.Version import net.fabricmc.loader.api.metadata.ModMetadata import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger +import org.spongepowered.asm.launch.MixinBootstrap import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job diff --git a/src/main/kotlin/apis/Profiles.kt b/src/main/kotlin/apis/Profiles.kt index 789364a..156de89 100644 --- a/src/main/kotlin/apis/Profiles.kt +++ b/src/main/kotlin/apis/Profiles.kt @@ -188,7 +188,7 @@ data class PlayerData( } @Serializable -data class AshconNameLookup( - val username: String, - val uuid: UUID, +data class MowojangNameLookup( + val name: String, + val id: UUID, ) diff --git a/src/main/kotlin/apis/Routes.kt b/src/main/kotlin/apis/Routes.kt index bf55a2d..5e29402 100644 --- a/src/main/kotlin/apis/Routes.kt +++ b/src/main/kotlin/apis/Routes.kt @@ -28,13 +28,13 @@ object Routes { return withContext(MinecraftDispatcher) { UUIDToName.computeIfAbsent(uuid) { async(Firmament.coroutineScope.coroutineContext) { - val response = Firmament.httpClient.get("https://api.ashcon.app/mojang/v2/user/$uuid") + val response = Firmament.httpClient.get("https://mowojang.matdoes.dev/$uuid") if (!response.status.isSuccess()) return@async null - val data = response.body<AshconNameLookup>() + val data = response.body<MowojangNameLookup>() launch(MinecraftDispatcher) { - nameToUUID[data.username] = async { data.uuid } + nameToUUID[data.name] = async { data.id } } - data.username + data.name } } }.await() @@ -44,13 +44,13 @@ object Routes { return withContext(MinecraftDispatcher) { nameToUUID.computeIfAbsent(name) { async(Firmament.coroutineScope.coroutineContext) { - val response = Firmament.httpClient.get("https://api.ashcon.app/mojang/v2/user/$name") + val response = Firmament.httpClient.get("https://mowojang.matdoes.dev/$name") if (!response.status.isSuccess()) return@async null - val data = response.body<AshconNameLookup>() + val data = response.body<MowojangNameLookup>() launch(MinecraftDispatcher) { - UUIDToName[data.uuid] = async { data.username } + UUIDToName[data.id] = async { data.name } } - data.uuid + data.id } } }.await() diff --git a/src/main/kotlin/commands/rome.kt b/src/main/kotlin/commands/rome.kt index c3eb03d..9fc5386 100644 --- a/src/main/kotlin/commands/rome.kt +++ b/src/main/kotlin/commands/rome.kt @@ -228,6 +228,15 @@ fun firmamentCommand() = literal("firmament") { } } } + thenLiteral("screens") { + thenExecute { + MC.sendChat(Text.literal(""" + |Screen: ${MC.screen} (${MC.screen?.title}) + |Screen Handler: ${MC.handledScreen?.screenHandler} ${MC.handledScreen?.screenHandler?.syncId} + |Player Screen Handler: ${MC.player?.currentScreenHandler} ${MC.player?.currentScreenHandler?.syncId} + """.trimMargin())) + } + } thenLiteral("blocks") { thenExecute { ScreenUtil.setScreenLater(MiningBlockInfoUi.makeScreen()) @@ -262,7 +271,8 @@ fun firmamentCommand() = literal("firmament") { source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.gametype", locrawInfo.gametype)) source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.mode", locrawInfo.mode)) source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.map", locrawInfo.map)) - source.sendFeedback(tr("firmament.sbinfo.custommining", "Custom Mining: ${formatBool(locrawInfo.skyblockLocation?.hasCustomMining ?: false)}")) + source.sendFeedback(tr("firmament.sbinfo.custommining", + "Custom Mining: ${formatBool(locrawInfo.skyblockLocation?.hasCustomMining ?: false)}")) } } } @@ -313,13 +323,15 @@ fun firmamentCommand() = literal("firmament") { } thenLiteral("mixins") { thenExecute { - source.sendFeedback(Text.translatable("firmament.mixins.start")) - MixinPlugin.appliedMixins - .map { it.removePrefix(MixinPlugin.mixinPackage) } - .forEach { - source.sendFeedback(Text.literal(" - ").withColor(0xD020F0) - .append(Text.literal(it).withColor(0xF6BA20))) - } + MixinPlugin.instances.forEach { plugin -> + source.sendFeedback(tr("firmament.mixins.start.package", "Mixins (base ${plugin.mixinPackage}):")) + plugin.appliedMixins + .map { it.removePrefix(plugin.mixinPackage) } + .forEach { + source.sendFeedback(Text.literal(" - ").withColor(0xD020F0) + .append(Text.literal(it).withColor(0xF6BA20))) + } + } } } thenLiteral("repo") { diff --git a/src/main/kotlin/events/CustomItemModelEvent.kt b/src/main/kotlin/events/CustomItemModelEvent.kt index 21ee326..7b86980 100644 --- a/src/main/kotlin/events/CustomItemModelEvent.kt +++ b/src/main/kotlin/events/CustomItemModelEvent.kt @@ -1,10 +1,13 @@ package moe.nea.firmament.events +import java.util.Objects import java.util.Optional import kotlin.jvm.optionals.getOrNull +import net.minecraft.component.DataComponentTypes import net.minecraft.item.ItemStack import net.minecraft.util.Identifier import moe.nea.firmament.util.collections.WeakCache +import moe.nea.firmament.util.collections.WeakCache.CacheFunction import moe.nea.firmament.util.mc.IntrospectableItemModelManager // TODO: assert an order on these events @@ -14,7 +17,36 @@ data class CustomItemModelEvent( var overrideModel: Identifier? = null, ) : FirmamentEvent() { companion object : FirmamentEventBus<CustomItemModelEvent>() { - val cache = WeakCache.memoize("ItemModelIdentifier", ::getModelIdentifier0) + val weakCache = + object : WeakCache<ItemStack, IntrospectableItemModelManager, Optional<Identifier>>("ItemModelIdentifier") { + override fun mkRef( + key: ItemStack, + extraData: IntrospectableItemModelManager + ): WeakCache<ItemStack, IntrospectableItemModelManager, Optional<Identifier>>.Ref { + return IRef(key, extraData) + } + + inner class IRef(weakInstance: ItemStack, data: IntrospectableItemModelManager) : + Ref(weakInstance, data) { + override fun shouldBeEvicted(): Boolean = false + val isSimpleStack = weakInstance.componentChanges.isEmpty || (weakInstance.componentChanges.size() == 1 && weakInstance.get( + DataComponentTypes.CUSTOM_DATA)?.isEmpty == true) + val item = weakInstance.item + override fun hashCode(): Int { + if (isSimpleStack) + return Objects.hash(item, extraData) + return super.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other is IRef && isSimpleStack) { + return other.isSimpleStack && item == other.item + } + return super.equals(other) + } + } + } + val cache = CacheFunction.WithExtraData(weakCache, ::getModelIdentifier0) @JvmStatic fun getModelIdentifier(itemStack: ItemStack?, itemModelManager: IntrospectableItemModelManager): Identifier? { diff --git a/src/main/kotlin/events/EntityUpdateEvent.kt b/src/main/kotlin/events/EntityUpdateEvent.kt index 27a90f9..fec2fa5 100644 --- a/src/main/kotlin/events/EntityUpdateEvent.kt +++ b/src/main/kotlin/events/EntityUpdateEvent.kt @@ -7,6 +7,8 @@ import net.minecraft.entity.LivingEntity import net.minecraft.entity.data.DataTracker import net.minecraft.item.ItemStack import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.util.MC /** * This event is fired when some entity properties are updated. @@ -15,7 +17,27 @@ import net.minecraft.network.packet.s2c.play.EntityAttributesS2CPacket * *after* the values have been applied to the entity. */ sealed class EntityUpdateEvent : FirmamentEvent() { - companion object : FirmamentEventBus<EntityUpdateEvent>() + companion object : FirmamentEventBus<EntityUpdateEvent>() { + @Subscribe + fun onPlayerInventoryUpdate(event: PlayerInventoryUpdate) { + val p = MC.player ?: return + val updatedSlots = listOf( + EquipmentSlot.HEAD to 39, + EquipmentSlot.CHEST to 38, + EquipmentSlot.LEGS to 37, + EquipmentSlot.FEET to 36, + EquipmentSlot.OFFHAND to 40, + EquipmentSlot.MAINHAND to p.inventory.selectedSlot, // TODO: also equipment update when you swap your selected slot perhaps + ).mapNotNull { (slot, stackIndex) -> + val slotIndex = p.playerScreenHandler.getSlotIndex(p.inventory, stackIndex).asInt + event.getOrNull(slotIndex)?.let { + Pair.of(slot, it) + } + } + if (updatedSlots.isNotEmpty()) + publish(EquipmentUpdate(p, updatedSlots)) + } + } abstract val entity: Entity diff --git a/src/main/kotlin/events/PlayerInventoryUpdate.kt b/src/main/kotlin/events/PlayerInventoryUpdate.kt index 6e8203a..88439a9 100644 --- a/src/main/kotlin/events/PlayerInventoryUpdate.kt +++ b/src/main/kotlin/events/PlayerInventoryUpdate.kt @@ -1,11 +1,22 @@ - package moe.nea.firmament.events import net.minecraft.item.ItemStack sealed class PlayerInventoryUpdate : FirmamentEvent() { - companion object : FirmamentEventBus<PlayerInventoryUpdate>() - data class Single(val slot: Int, val stack: ItemStack) : PlayerInventoryUpdate() - data class Multi(val contents: List<ItemStack>) : PlayerInventoryUpdate() + companion object : FirmamentEventBus<PlayerInventoryUpdate>() + data class Single(val slot: Int, val stack: ItemStack) : PlayerInventoryUpdate() { + override fun getOrNull(slot: Int): ItemStack? { + if (slot == this.slot) return stack + return null + } + + } + + data class Multi(val contents: List<ItemStack>) : PlayerInventoryUpdate() { + override fun getOrNull(slot: Int): ItemStack? { + return contents.getOrNull(slot) + } + } + abstract fun getOrNull(slot: Int): ItemStack? } diff --git a/src/main/kotlin/features/chat/ChatLinks.kt b/src/main/kotlin/features/chat/ChatLinks.kt index f85825b..a084234 100644 --- a/src/main/kotlin/features/chat/ChatLinks.kt +++ b/src/main/kotlin/features/chat/ChatLinks.kt @@ -3,6 +3,7 @@ package moe.nea.firmament.features.chat import io.ktor.client.request.get import io.ktor.client.statement.bodyAsChannel import io.ktor.utils.io.jvm.javaio.toInputStream +import java.net.URI import java.net.URL import java.util.Collections import java.util.concurrent.atomic.AtomicInteger @@ -78,7 +79,7 @@ object ChatLinks : FirmamentFeature { val texId = Firmament.identifier("dynamic_image_preview${nextTexId.getAndIncrement()}") MC.textureManager.registerTexture( texId, - NativeImageBackedTexture(image) + NativeImageBackedTexture({ texId.path }, image) ) Image(texId, image.width, image.height) } else @@ -102,8 +103,8 @@ object ChatLinks : FirmamentFeature { if (it.screen !is ChatScreen) return val hoveredComponent = MC.inGameHud.chatHud.getTextStyleAt(it.mouseX.toDouble(), it.mouseY.toDouble()) ?: return - val hoverEvent = hoveredComponent.hoverEvent ?: return - val value = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT) ?: return + val hoverEvent = hoveredComponent.hoverEvent as? HoverEvent.ShowText ?: return + val value = hoverEvent.value val url = urlRegex.matchEntire(value.unformattedString)?.groupValues?.get(0) ?: return if (!isImageUrl(url)) return val imageFuture = imageCache[url] ?: return @@ -149,8 +150,8 @@ object ChatLinks : FirmamentFeature { Text.literal(url).setStyle( Style.EMPTY.withUnderline(true).withColor( Formatting.AQUA - ).withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal(url))) - .withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, url)) + ).withHoverEvent(HoverEvent.ShowText(Text.literal(url))) + .withClickEvent(ClickEvent.OpenUrl(URI(url))) ) ) if (isImageUrl(url)) diff --git a/src/main/kotlin/features/debug/AnimatedClothingScanner.kt b/src/main/kotlin/features/debug/AnimatedClothingScanner.kt index 11b47a9..9f9f135 100644 --- a/src/main/kotlin/features/debug/AnimatedClothingScanner.kt +++ b/src/main/kotlin/features/debug/AnimatedClothingScanner.kt @@ -1,51 +1,193 @@ package moe.nea.firmament.features.debug -import net.minecraft.component.DataComponentTypes +import net.minecraft.command.argument.RegistryKeyArgumentType +import net.minecraft.component.ComponentType import net.minecraft.entity.Entity +import net.minecraft.entity.decoration.ArmorStandEntity +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtOps +import net.minecraft.registry.RegistryKeys import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.get +import moe.nea.firmament.commands.thenArgument import moe.nea.firmament.commands.thenExecute import moe.nea.firmament.commands.thenLiteral import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.events.EntityUpdateEvent +import moe.nea.firmament.events.WorldReadyEvent +import moe.nea.firmament.util.ClipboardUtils import moe.nea.firmament.util.MC -import moe.nea.firmament.util.skyBlockId +import moe.nea.firmament.util.math.GChainReconciliation +import moe.nea.firmament.util.math.GChainReconciliation.shortenCycle +import moe.nea.firmament.util.mc.NbtPrism import moe.nea.firmament.util.tr object AnimatedClothingScanner { - var observedEntity: Entity? = null + data class LensOfFashionTheft<T>( + val prism: NbtPrism, + val component: ComponentType<T>, + ) { + fun observe(itemStack: ItemStack): Collection<NbtElement> { + val x = itemStack.get(component) ?: return listOf() + val nbt = component.codecOrThrow.encodeStart(NbtOps.INSTANCE, x).orThrow + return prism.access(nbt) + } + } + + var lens: LensOfFashionTheft<*>? = null + var subject: Entity? = null + var history: MutableList<String> = mutableListOf() + val metaHistory: MutableList<List<String>> = mutableListOf() @OptIn(ExperimentalStdlibApi::class) @Subscribe fun onUpdate(event: EntityUpdateEvent) { - if (event.entity != observedEntity) return + val s = subject ?: return + if (event.entity != s) return + val l = lens ?: return if (event is EntityUpdateEvent.EquipmentUpdate) { event.newEquipment.forEach { - val id = it.second.skyBlockId?.neuItem - val colour = it.second.get(DataComponentTypes.DYED_COLOR) - ?.rgb?.toHexString(HexFormat.UpperCase) - ?.let { " #$it" } ?: "" - MC.sendChat(tr("firmament.fitstealer.update", - "[FIT CHECK][${MC.currentTick}] ${it.first.asString()} => ${id}${colour}")) + val formatted = (l.observe(it.second)).joinToString() + history.add(formatted) + // TODO: add a slot filter } } } + fun reduceHistory(reducer: (List<String>, List<String>) -> List<String>): List<String> { + return metaHistory.fold(history, reducer).shortenCycle() + } + @Subscribe fun onSubCommand(event: CommandEvent.SubCommand) { event.subcommand("dev") { thenLiteral("stealthisfit") { - thenExecute { - observedEntity = - if (observedEntity == null) MC.instance.targetedEntity else null - - MC.sendChat( - observedEntity?.let { - tr("firmament.fitstealer.targeted", "Observing the equipment of ${it.name}.") - } ?: tr("firmament.fitstealer.targetlost", "No longer logging equipment."), - ) + thenLiteral("clear") { + thenExecute { + subject = null + metaHistory.clear() + history.clear() + MC.sendChat(tr("firmament.fitstealer.clear", "Cleared fit stealing history")) + } + } + thenLiteral("copy") { + thenExecute { + val history = reduceHistory { a, b -> a + b } + copyHistory(history) + MC.sendChat(tr("firmament.fitstealer.copied", "Copied the history")) + } + thenLiteral("deduplicated") { + thenExecute { + val history = reduceHistory { a, b -> + (a.toMutableSet() + b).toList() + } + copyHistory(history) + MC.sendChat( + tr( + "firmament.fitstealer.copied.deduplicated", + "Copied the deduplicated history" + ) + ) + } + } + thenLiteral("merged") { + thenExecute { + val history = reduceHistory(GChainReconciliation::reconcileCycles) + copyHistory(history) + MC.sendChat(tr("firmament.fitstealer.copied.merged", "Copied the merged history")) + } + } + } + thenLiteral("target") { + thenLiteral("self") { + thenExecute { + toggleObserve(MC.player!!) + } + } + thenLiteral("pet") { + thenExecute { + source.sendFeedback( + tr( + "firmament.fitstealer.stealingpet", + "Observing nearest marker armourstand" + ) + ) + val p = MC.player!! + val nearestPet = p.world.getEntitiesByClass( + ArmorStandEntity::class.java, + p.boundingBox.expand(10.0), + { it.isMarker }) + .minBy { it.squaredDistanceTo(p) } + toggleObserve(nearestPet) + } + } + thenExecute { + val ent = MC.instance.targetedEntity + if (ent == null) { + source.sendFeedback( + tr( + "firmament.fitstealer.notargetundercursor", + "No entity under cursor" + ) + ) + } else { + toggleObserve(ent) + } + } + } + thenLiteral("path") { + thenArgument( + "component", + RegistryKeyArgumentType.registryKey(RegistryKeys.DATA_COMPONENT_TYPE) + ) { component -> + thenArgument("path", NbtPrism.Argument) { path -> + thenExecute { + lens = LensOfFashionTheft( + get(path), + MC.unsafeGetRegistryEntry(get(component))!!, + ) + source.sendFeedback( + tr( + "firmament.fitstealer.lensset", + "Analyzing path ${get(path)} for component ${get(component).value}" + ) + ) + } + } + } } } } } + + private fun copyHistory(toCopy: List<String>) { + ClipboardUtils.setTextContent(toCopy.joinToString("\n")) + } + + @Subscribe + fun onWorldSwap(event: WorldReadyEvent) { + subject = null + if (history.isNotEmpty()) { + metaHistory.add(history) + history = mutableListOf() + } + } + + private fun toggleObserve(entity: Entity?) { + subject = if (subject == null) entity else null + if (subject == null) { + metaHistory.add(history) + history = mutableListOf() + } + MC.sendChat( + subject?.let { + tr( + "firmament.fitstealer.targeted", + "Observing the equipment of ${it.name}." + ) + } ?: tr("firmament.fitstealer.targetlost", "No longer logging equipment."), + ) + } } diff --git a/src/main/kotlin/features/debug/DebugLogger.kt b/src/main/kotlin/features/debug/DebugLogger.kt index 2c6b962..9115956 100644 --- a/src/main/kotlin/features/debug/DebugLogger.kt +++ b/src/main/kotlin/features/debug/DebugLogger.kt @@ -10,6 +10,7 @@ class DebugLogger(val tag: String) { companion object { val allInstances = InstanceList<DebugLogger>("DebugLogger") } + object EnabledLogs : DataHolder<MutableSet<String>>(serializer(), "DebugLogs", ::mutableSetOf) init { @@ -17,6 +18,7 @@ class DebugLogger(val tag: String) { } fun isEnabled() = DeveloperFeatures.isEnabled && EnabledLogs.data.contains(tag) + fun log(text: String) = log { text } fun log(text: () -> String) { if (!isEnabled()) return MC.sendChat(Text.literal(text())) diff --git a/src/main/kotlin/features/debug/DeveloperFeatures.kt b/src/main/kotlin/features/debug/DeveloperFeatures.kt index 8f0c25c..af1e92e 100644 --- a/src/main/kotlin/features/debug/DeveloperFeatures.kt +++ b/src/main/kotlin/features/debug/DeveloperFeatures.kt @@ -3,6 +3,10 @@ package moe.nea.firmament.features.debug import java.io.File import java.nio.file.Path import java.util.concurrent.CompletableFuture +import org.objectweb.asm.ClassReader +import org.objectweb.asm.Type +import org.objectweb.asm.tree.ClassNode +import org.spongepowered.asm.mixin.Mixin import kotlinx.serialization.json.encodeToStream import kotlin.io.path.absolute import kotlin.io.path.exists @@ -10,11 +14,14 @@ import net.minecraft.client.MinecraftClient import net.minecraft.text.Text import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.DebugInstantiateEvent import moe.nea.firmament.events.TickEvent import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.init.MixinPlugin import moe.nea.firmament.util.MC import moe.nea.firmament.util.TimeMark +import moe.nea.firmament.util.asm.AsmAnnotationUtil import moe.nea.firmament.util.iterate object DeveloperFeatures : FirmamentFeature { @@ -42,6 +49,42 @@ object DeveloperFeatures : FirmamentFeature { } @Subscribe + fun loadAllMixinClasses(event: DebugInstantiateEvent) { + val allMixinClasses = mutableSetOf<String>() + MixinPlugin.instances.forEach { plugin -> + val prefix = plugin.mixinPackage + "." + val classes = plugin.mixins.map { prefix + it } + allMixinClasses.addAll(classes) + for (cls in classes) { + val targets = javaClass.classLoader.getResourceAsStream("${cls.replace(".", "/")}.class").use { + val node = ClassNode() + ClassReader(it).accept(node, 0) + val mixins = mutableListOf<Mixin>() + (node.visibleAnnotations.orEmpty() + node.invisibleAnnotations.orEmpty()).forEach { + val annotationType = Type.getType(it.desc) + val mixinType = Type.getType(Mixin::class.java) + if (mixinType == annotationType) { + mixins.add(AsmAnnotationUtil.createProxy(Mixin::class.java, it)) + } + } + mixins.flatMap { it.targets.toList() } + mixins.flatMap { it.value.map { it.java.name } } + } + for (target in targets) + try { + Firmament.logger.debug("Loading ${target} to force instantiate ${cls}") + Class.forName(target, true, javaClass.classLoader) + } catch (ex: Throwable) { + Firmament.logger.error("Could not load class ${target} that has been mixind by $cls", ex) + } + } + } + Firmament.logger.info("Forceloaded all Firmament mixins:") + val applied = MixinPlugin.instances.flatMap { it.appliedMixins }.toSet() + applied.forEach { Firmament.logger.info(" - ${it}") } + require(allMixinClasses == applied) + } + + @Subscribe fun dumpMissingTranslations(tickEvent: TickEvent) { val toDump = missingTranslations ?: return missingTranslations = null diff --git a/src/main/kotlin/features/debug/ExportedTestConstantMeta.kt b/src/main/kotlin/features/debug/ExportedTestConstantMeta.kt new file mode 100644 index 0000000..a817dd6 --- /dev/null +++ b/src/main/kotlin/features/debug/ExportedTestConstantMeta.kt @@ -0,0 +1,19 @@ +package moe.nea.firmament.features.debug + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import java.util.Optional + +data class ExportedTestConstantMeta( + val dataVersion: Int, + val modVersion: Optional<String>, +) { + companion object { + val CODEC: Codec<ExportedTestConstantMeta> = RecordCodecBuilder.create { + it.group( + Codec.INT.fieldOf("dataVersion").forGetter(ExportedTestConstantMeta::dataVersion), + Codec.STRING.optionalFieldOf("modVersion").forGetter(ExportedTestConstantMeta::modVersion), + ).apply(it, ::ExportedTestConstantMeta) + } + } +} diff --git a/src/main/kotlin/features/debug/PowerUserTools.kt b/src/main/kotlin/features/debug/PowerUserTools.kt index 8be5d5d..893b176 100644 --- a/src/main/kotlin/features/debug/PowerUserTools.kt +++ b/src/main/kotlin/features/debug/PowerUserTools.kt @@ -1,31 +1,25 @@ package moe.nea.firmament.features.debug -import com.mojang.serialization.Codec -import com.mojang.serialization.DynamicOps import com.mojang.serialization.JsonOps -import com.mojang.serialization.codecs.RecordCodecBuilder import kotlin.jvm.optionals.getOrNull import net.minecraft.block.SkullBlock import net.minecraft.block.entity.SkullBlockEntity import net.minecraft.component.DataComponentTypes import net.minecraft.component.type.ProfileComponent import net.minecraft.entity.Entity -import net.minecraft.entity.EntityType import net.minecraft.entity.LivingEntity import net.minecraft.item.ItemStack import net.minecraft.item.Items -import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtList import net.minecraft.nbt.NbtOps -import net.minecraft.nbt.NbtString import net.minecraft.predicate.NbtPredicate import net.minecraft.text.Text import net.minecraft.text.TextCodecs import net.minecraft.util.Identifier +import net.minecraft.util.Nameable import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.hit.EntityHitResult import net.minecraft.util.hit.HitResult -import net.minecraft.util.math.Position -import net.minecraft.util.math.Vec3d import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.CustomItemModelEvent import moe.nea.firmament.events.HandledScreenKeyPressedEvent @@ -43,8 +37,10 @@ import moe.nea.firmament.util.mc.IntrospectableItemModelManager import moe.nea.firmament.util.mc.SNbtFormatter import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString import moe.nea.firmament.util.mc.displayNameAccordingToNbt +import moe.nea.firmament.util.mc.iterableArmorItems import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.skyBlockId +import moe.nea.firmament.util.tr object PowerUserTools : FirmamentFeature { override val identifier: String @@ -59,6 +55,7 @@ object PowerUserTools : FirmamentFeature { val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture") val copyEntityData by keyBindingWithDefaultUnbound("entity-data") val copyItemStack by keyBindingWithDefaultUnbound("copy-item-stack") + val copyTitle by keyBindingWithDefaultUnbound("copy-title") } override val config @@ -108,7 +105,7 @@ object PowerUserTools : FirmamentFeature { MC.sendChat(Text.stringifiedTranslatable("firmament.poweruser.entity.position", target.pos)) if (target is LivingEntity) { MC.sendChat(Text.translatable("firmament.poweruser.entity.armor")) - for (armorItem in target.armorItems) { + for ((slot, armorItem) in target.iterableArmorItems) { MC.sendChat(Text.translatable("firmament.poweruser.entity.armor.item", debugFormat(armorItem))) } } @@ -179,11 +176,23 @@ object PowerUserTools : FirmamentFeature { Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.skull-id", skullTexture.toString())) println("Copied skull id: $skullTexture") } else if (it.matches(TConfig.copyItemStack)) { - ClipboardUtils.setTextContent( - ItemStack.CODEC - .encodeStart(MC.currentOrDefaultRegistries.getOps(NbtOps.INSTANCE), item) - .orThrow.toPrettyString()) + val nbt = ItemStack.CODEC + .encodeStart(MC.currentOrDefaultRegistries.getOps(NbtOps.INSTANCE), item) + .orThrow + ClipboardUtils.setTextContent(nbt.toPrettyString()) lastCopiedStack = Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.stack")) + } else if (it.matches(TConfig.copyTitle)) { + val allTitles = NbtList() + val inventoryNames = + it.screen.screenHandler.slots + .mapNotNullTo(mutableSetOf()) { it.inventory } + .filterIsInstance<Nameable>() + .map { it.name } + for (it in listOf(it.screen.title) + inventoryNames) { + allTitles.add(TextCodecs.CODEC.encodeStart(NbtOps.INSTANCE, it).result().getOrNull()!!) + } + ClipboardUtils.setTextContent(allTitles.toPrettyString()) + MC.sendChat(tr("firmament.power-user.title.copied", "Copied screen and inventory titles")) } } diff --git a/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt b/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt index 1824225..cfc05cc 100644 --- a/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt +++ b/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt @@ -222,7 +222,7 @@ object MinesweeperHelper { fun onChat(event: ProcessChatEvent) { if (CarnivalFeatures.TConfig.displayTutorials && event.unformattedString == startGameQuestion) { MC.sendChat(Text.translatable("firmament.carnival.tutorial.minesweeper").styled { - it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, "/firm minesweepertutorial")) + it.withClickEvent(ClickEvent.RunCommand("/firm minesweepertutorial")) }) } if (!CarnivalFeatures.TConfig.enableBombSolver) { @@ -259,7 +259,7 @@ object MinesweeperHelper { val boardPosition = BoardPosition.fromBlockPos(event.blockPos) log.log { "Breaking block at ${event.blockPos} ($boardPosition)" } gs.lastClickedPosition = boardPosition - gs.lastDowsingMode = DowsingMode.fromItem(event.player.inventory.mainHandStack) + gs.lastDowsingMode = DowsingMode.fromItem(event.player.mainHandStack) } @Subscribe diff --git a/src/main/kotlin/features/fixes/Fixes.kt b/src/main/kotlin/features/fixes/Fixes.kt index 3dae233..5e6350d 100644 --- a/src/main/kotlin/features/fixes/Fixes.kt +++ b/src/main/kotlin/features/fixes/Fixes.kt @@ -24,6 +24,7 @@ object Fixes : FirmamentFeature { val peekChat by keyBindingWithDefaultUnbound("peek-chat") val hidePotionEffects by toggle("hide-mob-effects") { false } val noHurtCam by toggle("disable-hurt-cam") { false } + val hideSlotHighlights by toggle("hide-slot-highlights") { false } } override val config: ManagedConfig @@ -50,7 +51,7 @@ object Fixes : FirmamentFeature { "firmament.fixes.auto-sprint.sprinting" else "firmament.fixes.auto-sprint.not-sprinting" - ), 0, 0, -1, false + ), 0, 0, -1, true ) it.context.matrices.pop() } diff --git a/src/main/kotlin/features/inventory/PetFeatures.kt b/src/main/kotlin/features/inventory/PetFeatures.kt index 5ca10f7..bb39fbc 100644 --- a/src/main/kotlin/features/inventory/PetFeatures.kt +++ b/src/main/kotlin/features/inventory/PetFeatures.kt @@ -1,14 +1,24 @@ package moe.nea.firmament.features.inventory -import net.minecraft.util.Identifier +import moe.nea.jarvis.api.Point +import net.minecraft.item.ItemStack +import net.minecraft.text.Text +import net.minecraft.util.Formatting +import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.HudRenderEvent import moe.nea.firmament.events.SlotRenderEvents import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.FirmFormatters.formatPercent +import moe.nea.firmament.util.FirmFormatters.shortFormat import moe.nea.firmament.util.MC import moe.nea.firmament.util.petData import moe.nea.firmament.util.render.drawGuiTexture +import moe.nea.firmament.util.skyblock.Rarity +import moe.nea.firmament.util.titleCase import moe.nea.firmament.util.useMatch +import moe.nea.firmament.util.withColor object PetFeatures : FirmamentFeature { override val identifier: String @@ -19,9 +29,12 @@ object PetFeatures : FirmamentFeature { object TConfig : ManagedConfig(identifier, Category.INVENTORY) { val highlightEquippedPet by toggle("highlight-pet") { true } + var petOverlay by toggle("pet-overlay") { false } + val petOverlayHud by position("pet-overlay-hud", 80, 10) { Point(0.5, 1.0) } } val petMenuTitle = "Pets(?: \\([0-9]+/[0-9]+\\))?".toPattern() + var petItemStack: ItemStack? = null @Subscribe fun onSlotRender(event: SlotRenderEvents.Before) { @@ -29,12 +42,44 @@ object PetFeatures : FirmamentFeature { val stack = event.slot.stack if (stack.petData?.active == true) petMenuTitle.useMatch(MC.screenName ?: return) { - event.context.drawGuiTexture( - event.slot.x, event.slot.y, 0, 16, 16, - Identifier.of("firmament:selected_pet_background") - ) - } + petItemStack = stack + event.context.drawGuiTexture( + Firmament.identifier("selected_pet_background"), + event.slot.x, event.slot.y, 16, 16, + ) + } } + @Subscribe + fun onRenderHud(it: HudRenderEvent) { + if (!TConfig.petOverlay) return + val itemStack = petItemStack ?: return + val petData = petItemStack?.petData ?: return + val rarity = Rarity.fromNeuRepo(petData.tier) + val rarityCode = Rarity.colourMap[rarity] ?: Formatting.WHITE + val xp = petData.level + val petType = titleCase(petData.type) + val heldItem = petData.heldItem?.let { item -> "Held Item: ${titleCase(item)}" } + + it.context.matrices.push() + TConfig.petOverlayHud.applyTransformations(it.context.matrices) + + val lines = mutableListOf<Text>() + it.context.matrices.push() + it.context.matrices.translate(-0.5, -0.5, 0.0) + it.context.matrices.scale(2f, 2f, 1f) + it.context.drawItem(itemStack, 0, 0) + it.context.matrices.pop() + lines.add(Text.literal("[Lvl ${xp.currentLevel}] ").append(Text.literal(petType).withColor(rarityCode))) + if (heldItem != null) lines.add(Text.literal(heldItem)) + if (xp.currentLevel != xp.maxLevel) lines.add(Text.literal("Required L${xp.currentLevel + 1}: ${shortFormat(xp.expInCurrentLevel.toDouble())}/${shortFormat(xp.expRequiredForNextLevel.toDouble())} (${formatPercent(xp.percentageToNextLevel.toDouble())})")) + lines.add(Text.literal("Required L100: ${shortFormat(xp.expTotal.toDouble())}/${shortFormat(xp.expRequiredForMaxLevel.toDouble())} (${formatPercent(xp.percentageToMaxLevel.toDouble())})")) + + for ((index, line) in lines.withIndex()) { + it.context.drawText(MC.font, line.copy().withColor(Formatting.GRAY), 36, MC.font.fontHeight * index, -1, true) + } + + it.context.matrices.pop() + } } diff --git a/src/main/kotlin/features/inventory/REIDependencyWarner.kt b/src/main/kotlin/features/inventory/REIDependencyWarner.kt index 1e9b1b8..7d88dd1 100644 --- a/src/main/kotlin/features/inventory/REIDependencyWarner.kt +++ b/src/main/kotlin/features/inventory/REIDependencyWarner.kt @@ -1,5 +1,6 @@ package moe.nea.firmament.features.inventory +import java.net.URI import net.fabricmc.loader.api.FabricLoader import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -38,7 +39,7 @@ object REIDependencyWarner { .white() .append(Text.literal("[").aqua()) .append(Text.translatable("firmament.download", modName) - .styled { it.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, modrinthLink(slug))) } + .styled { it.withClickEvent(ClickEvent.OpenUrl(URI (modrinthLink(slug)))) } .yellow() .also { if (alreadyDownloaded) diff --git a/src/main/kotlin/features/inventory/SlotLocking.kt b/src/main/kotlin/features/inventory/SlotLocking.kt index 0083c40..d3348a2 100644 --- a/src/main/kotlin/features/inventory/SlotLocking.kt +++ b/src/main/kotlin/features/inventory/SlotLocking.kt @@ -4,6 +4,7 @@ package moe.nea.firmament.features.inventory import java.util.UUID import org.lwjgl.glfw.GLFW +import util.render.CustomRenderLayers import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers @@ -17,6 +18,9 @@ import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.int import kotlinx.serialization.serializer import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.client.render.RenderLayer +import net.minecraft.client.render.RenderLayers +import net.minecraft.client.render.TexturedRenderLayers import net.minecraft.entity.player.PlayerInventory import net.minecraft.screen.GenericContainerScreenHandler import net.minecraft.screen.slot.Slot @@ -45,7 +49,6 @@ import moe.nea.firmament.util.mc.ScreenUtil.getSlotByIndex import moe.nea.firmament.util.mc.SlotUtils.swapWithHotBar import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt -import moe.nea.firmament.util.render.GuiRenderLayers import moe.nea.firmament.util.render.drawLine import moe.nea.firmament.util.skyblock.DungeonUtil import moe.nea.firmament.util.skyblockUUID @@ -445,7 +448,7 @@ object SlotLocking : FirmamentFeature { val isUUIDLocked = (it.slot.stack?.skyblockUUID) in (lockedUUIDs ?: setOf()) if (isSlotLocked || isUUIDLocked) { it.context.drawGuiTexture( - GuiRenderLayers.GUI_TEXTURED_NO_DEPTH, + RenderLayer::getGuiTexturedOverlay, when { isSlotLocked -> (Identifier.of("firmament:slot_locked")) diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt index 8fad4df..d7346c2 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageBackingHandle.kt @@ -32,8 +32,8 @@ sealed interface StorageBackingHandle { StorageBackingHandle, HasBackingScreen companion object { - private val enderChestName = "^Ender Chest \\(([1-9])/[1-9]\\)$".toRegex() - private val backPackName = "^.+Backpack \\(Slot #([0-9]+)\\)$".toRegex() + private val enderChestName = "^Ender Chest (?:✦ )?\\(([1-9])/[1-9]\\)$".toRegex() + private val backPackName = "^.+Backpack (?:✦ )?\\(Slot #([0-9]+)\\)$".toRegex() /** * Parse a screen into a [StorageBackingHandle]. If this returns null it means that the screen is not diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt index 2e807de..2101915 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlay.kt @@ -33,6 +33,7 @@ object StorageOverlay : FirmamentFeature { val inverseScroll by toggle("inverse-scroll") { false } val padding by integer("padding", 1, 20) { 5 } val margin by integer("margin", 1, 60) { 20 } + val itemsBlockScrolling by toggle("block-item-scrolling") { true } } fun adjustScrollSpeed(amount: Double): Double { diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt index 6092e26..81f058e 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayCustom.kt @@ -9,6 +9,7 @@ import net.minecraft.entity.player.PlayerInventory import net.minecraft.screen.slot.Slot import moe.nea.firmament.mixins.accessor.AccessorHandledScreen import moe.nea.firmament.util.customgui.CustomGui +import moe.nea.firmament.util.focusedItemStack class StorageOverlayCustom( val handler: StorageBackingHandle, @@ -113,6 +114,8 @@ class StorageOverlayCustom( horizontalAmount: Double, verticalAmount: Double ): Boolean { + if (screen.focusedItemStack != null && StorageOverlay.TConfig.itemsBlockScrolling) + return false return overview.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount) } } diff --git a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt index 63a2f54..22f4dab 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/StorageOverlayScreen.kt @@ -218,7 +218,7 @@ class StorageOverlayScreen : Screen(Text.literal("")) { } fun drawPlayerInventory(context: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { - val items = MC.player?.inventory?.main ?: return + val items = MC.player?.inventory?.mainStacks ?: return items.withIndex().forEach { (index, item) -> val (x, y) = getPlayerInventorySlotPosition(index) context.drawItem(item, x, y, 0) diff --git a/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt index 3b86184..d99acd7 100644 --- a/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt +++ b/src/main/kotlin/features/inventory/storageoverlay/VirtualInventory.kt @@ -11,6 +11,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlin.jvm.optionals.getOrNull import net.minecraft.item.ItemStack import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtIo @@ -42,15 +43,15 @@ data class VirtualInventory( override fun deserialize(decoder: Decoder): VirtualInventory { val s = decoder.decodeString() val n = NbtIo.readCompressed(ByteArrayInputStream(s.decodeBase64Bytes()), NbtSizeTracker.of(100_000_000)) - val items = n.getList(INVENTORY, NbtCompound.COMPOUND_TYPE.toInt()) + val items = n.getList(INVENTORY).getOrNull() val ops = getOps() - return VirtualInventory(items.map { + return VirtualInventory(items?.map { it as NbtCompound if (it.isEmpty) ItemStack.EMPTY else ErrorUtil.catch("Could not deserialize item") { ItemStack.CODEC.parse(ops, it).orThrow }.or { ItemStack.EMPTY } - }) + } ?: listOf()) } fun getOps() = TolerantRegistriesOps(NbtOps.INSTANCE, MC.currentOrDefaultRegistries) diff --git a/src/main/kotlin/features/mining/PickaxeAbility.kt b/src/main/kotlin/features/mining/PickaxeAbility.kt index 1737969..d39694a 100644 --- a/src/main/kotlin/features/mining/PickaxeAbility.kt +++ b/src/main/kotlin/features/mining/PickaxeAbility.kt @@ -1,9 +1,13 @@ package moe.nea.firmament.features.mining import java.util.regex.Pattern +import kotlin.jvm.optionals.getOrNull import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds +import net.minecraft.client.MinecraftClient +import net.minecraft.client.toast.SystemToast import net.minecraft.item.ItemStack +import net.minecraft.text.Text import net.minecraft.util.DyeColor import net.minecraft.util.Hand import net.minecraft.util.Identifier @@ -47,6 +51,7 @@ object PickaxeAbility : FirmamentFeature { object TConfig : ManagedConfig(identifier, Category.MINING) { val cooldownEnabled by toggle("ability-cooldown") { false } val cooldownScale by integer("ability-scale", 16, 64) { 16 } + val cooldownReadyToast by toggle("ability-cooldown-toast") { false } val drillFuelBar by toggle("fuel-bar") { true } val blockOnPrivateIsland by choice( "block-on-dynamic", @@ -140,8 +145,7 @@ object PickaxeAbility : FirmamentFeature { } } ?: return val extra = it.item.extraAttributes - if (!extra.contains("drill_fuel")) return - val fuel = extra.getInt("drill_fuel") + val fuel = extra.getInt("drill_fuel").getOrNull() ?: return val percentage = fuel / maxFuel.toFloat() it.barOverride = DurabilityBarEvent.DurabilityBar( lerp( @@ -170,6 +174,11 @@ object PickaxeAbility : FirmamentFeature { nowAvailable.useMatch(it.unformattedString) { val ability = group("name") lastUsage[ability] = TimeMark.farPast() + if (!TConfig.cooldownReadyToast) return + val mc: MinecraftClient = MinecraftClient.getInstance() + mc.toastManager.add( + SystemToast.create(mc, SystemToast.Type.NARRATOR_TOGGLE, tr("firmament.pickaxe.ability-ready","Pickaxe Cooldown"), tr("firmament.pickaxe.ability-ready.desc", "Pickaxe ability is ready!")) + ) } } diff --git a/src/main/kotlin/features/misc/LicenseViewer.kt b/src/main/kotlin/features/misc/LicenseViewer.kt new file mode 100644 index 0000000..4219177 --- /dev/null +++ b/src/main/kotlin/features/misc/LicenseViewer.kt @@ -0,0 +1,128 @@ +package moe.nea.firmament.features.misc + +import io.github.notenoughupdates.moulconfig.observer.ObservableList +import io.github.notenoughupdates.moulconfig.xml.Bind +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.json.decodeFromStream +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.commands.thenExecute +import moe.nea.firmament.events.CommandEvent +import moe.nea.firmament.util.ErrorUtil +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.MoulConfigUtils +import moe.nea.firmament.util.ScreenUtil +import moe.nea.firmament.util.tr + +object LicenseViewer { + @Serializable + data class Software( + val licenses: List<License> = listOf(), + val webPresence: String? = null, + val projectName: String, + val projectDescription: String? = null, + val developers: List<Developer> = listOf(), + ) { + + @Bind + fun hasWebPresence() = webPresence != null + + @Bind + fun webPresence() = webPresence ?: "<no web presence>" + @Bind + fun open() { + MC.openUrl(webPresence ?: return) + } + + @Bind + fun projectName() = projectName + + @Bind + fun projectDescription() = projectDescription ?: "<no project description>" + + @get:Bind("developers") + @Transient + val developersO = ObservableList(developers) + + @get:Bind("licenses") + @Transient + val licenses0 = ObservableList(licenses) + } + + @Serializable + data class Developer( + @get:Bind("name") val name: String, + val webPresence: String? = null + ) { + + @Bind + fun open() { + MC.openUrl(webPresence ?: return) + } + + @Bind + fun hasWebPresence() = webPresence != null + + @Bind + fun webPresence() = webPresence ?: "<no web presence>" + } + + @Serializable + data class License( + @get:Bind("name") val licenseName: String, + val licenseUrl: String? = null + ) { + @Bind + fun open() { + MC.openUrl(licenseUrl ?: return) + } + + @Bind + fun hasUrl() = licenseUrl != null + + @Bind + fun url() = licenseUrl ?: "<no link to license text>" + } + + data class LicenseList( + val softwares: List<Software> + ) { + @get:Bind("softwares") + val obs = ObservableList(softwares) + } + + @OptIn(ExperimentalSerializationApi::class) + val licenses: LicenseList? = ErrorUtil.catch("Could not load licenses") { + Firmament.json.decodeFromStream<List<Software>?>( + javaClass.getResourceAsStream("/LICENSES-FIRMAMENT.json") ?: error("Could not find LICENSES-FIRMAMENT.json") + )?.let { LicenseList(it) } + }.orNull() + + fun showLicenses() { + ErrorUtil.catch("Could not display licenses") { + ScreenUtil.setScreenLater( + MoulConfigUtils.loadScreen( + "license_viewer/index", licenses!!, null + ) + ) + }.or { + MC.sendChat( + tr( + "firmament.licenses.notfound", + "Could not load licenses. Please check the Firmament source code for information directly." + ) + ) + } + } + + @Subscribe + fun onSubcommand(event: CommandEvent.SubCommand) { + event.subcommand("licenses") { + thenExecute { + showLicenses() + } + } + } +} diff --git a/src/main/kotlin/gui/entity/EntityRenderer.kt b/src/main/kotlin/gui/entity/EntityRenderer.kt index fd7a0c4..b4c1c7f 100644 --- a/src/main/kotlin/gui/entity/EntityRenderer.kt +++ b/src/main/kotlin/gui/entity/EntityRenderer.kt @@ -169,13 +169,13 @@ object EntityRenderer { val oldBodyYaw = entity.bodyYaw val oldYaw = entity.yaw val oldPitch = entity.pitch - val oldPrevHeadYaw = entity.prevHeadYaw + val oldPrevHeadYaw = entity.lastHeadYaw val oldHeadYaw = entity.headYaw entity.bodyYaw = 180.0f + targetYaw * 20.0f entity.yaw = 180.0f + targetYaw * 40.0f entity.pitch = -targetPitch * 20.0f entity.headYaw = entity.yaw - entity.prevHeadYaw = entity.yaw + entity.lastHeadYaw = entity.yaw val vector3f = Vector3f(0.0f, (entity.height / 2.0f + bottomOffset).toFloat(), 0.0f) InventoryScreen.drawEntity( context, @@ -190,7 +190,7 @@ object EntityRenderer { entity.bodyYaw = oldBodyYaw entity.yaw = oldYaw entity.pitch = oldPitch - entity.prevHeadYaw = oldPrevHeadYaw + entity.lastHeadYaw = oldPrevHeadYaw entity.headYaw = oldHeadYaw context.disableScissor() } diff --git a/src/main/kotlin/gui/entity/FakeWorld.kt b/src/main/kotlin/gui/entity/FakeWorld.kt deleted file mode 100644 index ccf6b60..0000000 --- a/src/main/kotlin/gui/entity/FakeWorld.kt +++ /dev/null @@ -1,343 +0,0 @@ -package moe.nea.firmament.gui.entity - -import java.util.UUID -import java.util.function.BooleanSupplier -import java.util.function.Consumer -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.client.gui.screen.world.SelectWorldScreen -import net.minecraft.component.type.MapIdComponent -import net.minecraft.entity.Entity -import net.minecraft.entity.boss.dragon.EnderDragonPart -import net.minecraft.entity.damage.DamageSource -import net.minecraft.entity.player.PlayerEntity -import net.minecraft.fluid.Fluid -import net.minecraft.item.FuelRegistry -import net.minecraft.item.map.MapState -import net.minecraft.particle.ParticleEffect -import net.minecraft.recipe.BrewingRecipeRegistry -import net.minecraft.recipe.RecipeManager -import net.minecraft.recipe.RecipePropertySet -import net.minecraft.recipe.StonecuttingRecipe -import net.minecraft.recipe.display.CuttingRecipeDisplay -import net.minecraft.registry.DynamicRegistryManager -import net.minecraft.registry.Registries -import net.minecraft.registry.RegistryKey -import net.minecraft.registry.RegistryKeys -import net.minecraft.registry.ServerDynamicRegistryType -import net.minecraft.registry.entry.RegistryEntry -import net.minecraft.resource.DataConfiguration -import net.minecraft.resource.ResourcePackManager -import net.minecraft.resource.featuretoggle.FeatureFlags -import net.minecraft.resource.featuretoggle.FeatureSet -import net.minecraft.scoreboard.Scoreboard -import net.minecraft.server.SaveLoading -import net.minecraft.server.command.CommandManager -import net.minecraft.sound.SoundCategory -import net.minecraft.sound.SoundEvent -import net.minecraft.util.Identifier -import net.minecraft.util.TypeFilter -import net.minecraft.util.function.LazyIterationConsumer -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.Box -import net.minecraft.util.math.ChunkPos -import net.minecraft.util.math.Direction -import net.minecraft.util.math.Vec3d -import net.minecraft.world.BlockView -import net.minecraft.world.Difficulty -import net.minecraft.world.MutableWorldProperties -import net.minecraft.world.World -import net.minecraft.world.biome.Biome -import net.minecraft.world.biome.BiomeKeys -import net.minecraft.world.chunk.Chunk -import net.minecraft.world.chunk.ChunkManager -import net.minecraft.world.chunk.ChunkStatus -import net.minecraft.world.chunk.EmptyChunk -import net.minecraft.world.chunk.light.LightingProvider -import net.minecraft.world.entity.EntityLookup -import net.minecraft.world.event.GameEvent -import net.minecraft.world.explosion.ExplosionBehavior -import net.minecraft.world.tick.OrderedTick -import net.minecraft.world.tick.QueryableTickScheduler -import net.minecraft.world.tick.TickManager -import moe.nea.firmament.util.MC - -fun createDynamicRegistry(): DynamicRegistryManager.Immutable { - // TODO: use SaveLoading.load() to properly load a full registry - return DynamicRegistryManager.of(Registries.REGISTRIES) -} - -class FakeWorld( - registries: DynamicRegistryManager.Immutable = createDynamicRegistry(), -) : World( - Properties, - RegistryKey.of(RegistryKeys.WORLD, Identifier.of("firmament", "fakeworld")), - registries, - MC.defaultRegistries.getOrThrow(RegistryKeys.DIMENSION_TYPE) - .getOrThrow(RegistryKey.of(RegistryKeys.DIMENSION_TYPE, Identifier.of("minecraft", "overworld"))), - true, - false, - 0L, - 0 -) { - object Properties : MutableWorldProperties { - override fun getSpawnPos(): BlockPos { - return BlockPos.ORIGIN - } - - override fun getSpawnAngle(): Float { - return 0F - } - - override fun getTime(): Long { - return 0 - } - - override fun getTimeOfDay(): Long { - return 0 - } - - override fun isThundering(): Boolean { - return false - } - - override fun isRaining(): Boolean { - return false - } - - override fun setRaining(raining: Boolean) { - } - - override fun isHardcore(): Boolean { - return false - } - - override fun getDifficulty(): Difficulty { - return Difficulty.HARD - } - - override fun isDifficultyLocked(): Boolean { - return false - } - - override fun setSpawnPos(pos: BlockPos?, angle: Float) {} - } - - override fun getPlayers(): List<PlayerEntity> { - return emptyList() - } - - override fun getBrightness(direction: Direction?, shaded: Boolean): Float { - return 1f - } - - override fun getGeneratorStoredBiome(biomeX: Int, biomeY: Int, biomeZ: Int): RegistryEntry<Biome> { - return registryManager.getOptionalEntry(BiomeKeys.PLAINS).get() - } - - override fun getSeaLevel(): Int { - return 0 - } - - override fun getEnabledFeatures(): FeatureSet { - return FeatureFlags.VANILLA_FEATURES - } - - class FakeTickScheduler<T> : QueryableTickScheduler<T> { - override fun scheduleTick(orderedTick: OrderedTick<T>?) { - } - - override fun isQueued(pos: BlockPos?, type: T): Boolean { - return true - } - - override fun getTickCount(): Int { - return 0 - } - - override fun isTicking(pos: BlockPos?, type: T): Boolean { - return true - } - - } - - override fun getBlockTickScheduler(): QueryableTickScheduler<Block> { - return FakeTickScheduler() - } - - override fun getFluidTickScheduler(): QueryableTickScheduler<Fluid> { - return FakeTickScheduler() - } - - - class FakeChunkManager(val world: FakeWorld) : ChunkManager() { - override fun getChunk(x: Int, z: Int, leastStatus: ChunkStatus?, create: Boolean): Chunk { - return EmptyChunk( - world, - ChunkPos(x, z), - world.registryManager.getOptionalEntry(BiomeKeys.PLAINS).get() - ) - } - - override fun getWorld(): BlockView { - return world - } - - override fun tick(shouldKeepTicking: BooleanSupplier?, tickChunks: Boolean) { - } - - override fun getDebugString(): String { - return "FakeChunkManager" - } - - override fun getLoadedChunkCount(): Int { - return 0 - } - - override fun getLightingProvider(): LightingProvider { - return FakeLightingProvider(this) - } - } - - class FakeLightingProvider(chunkManager: FakeChunkManager) : LightingProvider(chunkManager, false, false) - - override fun getChunkManager(): ChunkManager { - return FakeChunkManager(this) - } - - override fun playSound( - source: PlayerEntity?, - x: Double, - y: Double, - z: Double, - sound: RegistryEntry<SoundEvent>?, - category: SoundCategory?, - volume: Float, - pitch: Float, - seed: Long - ) { - } - - override fun syncWorldEvent(player: PlayerEntity?, eventId: Int, pos: BlockPos?, data: Int) { - } - - override fun emitGameEvent(event: RegistryEntry<GameEvent>?, emitterPos: Vec3d?, emitter: GameEvent.Emitter?) { - } - - override fun updateListeners(pos: BlockPos?, oldState: BlockState?, newState: BlockState?, flags: Int) { - } - - override fun playSoundFromEntity( - source: PlayerEntity?, - entity: Entity?, - sound: RegistryEntry<SoundEvent>?, - category: SoundCategory?, - volume: Float, - pitch: Float, - seed: Long - ) { - } - - override fun createExplosion( - entity: Entity?, - damageSource: DamageSource?, - behavior: ExplosionBehavior?, - x: Double, - y: Double, - z: Double, - power: Float, - createFire: Boolean, - explosionSourceType: ExplosionSourceType?, - smallParticle: ParticleEffect?, - largeParticle: ParticleEffect?, - soundEvent: RegistryEntry<SoundEvent>? - ) { - TODO("Not yet implemented") - } - - override fun asString(): String { - return "FakeWorld" - } - - override fun getEntityById(id: Int): Entity? { - return null - } - - override fun getEnderDragonParts(): MutableCollection<EnderDragonPart> { - return mutableListOf() - } - - override fun getTickManager(): TickManager { - return TickManager() - } - - override fun getMapState(id: MapIdComponent?): MapState? { - return null - } - - override fun putMapState(id: MapIdComponent?, state: MapState?) { - } - - override fun increaseAndGetMapId(): MapIdComponent { - return MapIdComponent(0) - } - - override fun setBlockBreakingInfo(entityId: Int, pos: BlockPos?, progress: Int) { - } - - override fun getScoreboard(): Scoreboard { - return Scoreboard() - } - - override fun getRecipeManager(): RecipeManager { - return object : RecipeManager { - override fun getPropertySet(key: RegistryKey<RecipePropertySet>?): RecipePropertySet { - return RecipePropertySet.EMPTY - } - - override fun getStonecutterRecipes(): CuttingRecipeDisplay.Grouping<StonecuttingRecipe> { - return CuttingRecipeDisplay.Grouping.empty() - } - } - } - - object FakeEntityLookup : EntityLookup<Entity> { - override fun get(id: Int): Entity? { - return null - } - - override fun get(uuid: UUID?): Entity? { - return null - } - - override fun iterate(): MutableIterable<Entity> { - return mutableListOf() - } - - override fun <U : Entity?> forEachIntersects( - filter: TypeFilter<Entity, U>?, - box: Box?, - consumer: LazyIterationConsumer<U>? - ) { - } - - override fun forEachIntersects(box: Box?, action: Consumer<Entity>?) { - } - - override fun <U : Entity?> forEach(filter: TypeFilter<Entity, U>?, consumer: LazyIterationConsumer<U>?) { - } - - } - - override fun getEntityLookup(): EntityLookup<Entity> { - return FakeEntityLookup - } - - override fun getBrewingRecipeRegistry(): BrewingRecipeRegistry { - return BrewingRecipeRegistry.EMPTY - } - - override fun getFuelRegistry(): FuelRegistry { - TODO("Not yet implemented") - } -} diff --git a/src/main/kotlin/gui/entity/ModifyEquipment.kt b/src/main/kotlin/gui/entity/ModifyEquipment.kt index a558936..712f5ca 100644 --- a/src/main/kotlin/gui/entity/ModifyEquipment.kt +++ b/src/main/kotlin/gui/entity/ModifyEquipment.kt @@ -47,7 +47,7 @@ object ModifyEquipment : EntityModifier { private fun coloredLeatherArmor(leatherArmor: Item, data: String): ItemStack { val stack = ItemStack(leatherArmor) - stack.set(DataComponentTypes.DYED_COLOR, DyedColorComponent(data.toInt(16), false)) + stack.set(DataComponentTypes.DYED_COLOR, DyedColorComponent(data.toInt(16))) return stack } } diff --git a/src/main/kotlin/gui/entity/ModifyHorse.kt b/src/main/kotlin/gui/entity/ModifyHorse.kt index f094ca4..7c8baa7 100644 --- a/src/main/kotlin/gui/entity/ModifyHorse.kt +++ b/src/main/kotlin/gui/entity/ModifyHorse.kt @@ -1,4 +1,3 @@ - package moe.nea.firmament.gui.entity import com.google.gson.JsonNull @@ -7,6 +6,7 @@ import kotlin.experimental.and import kotlin.experimental.inv import kotlin.experimental.or import net.minecraft.entity.EntityType +import net.minecraft.entity.EquipmentSlot import net.minecraft.entity.LivingEntity import net.minecraft.entity.SpawnReason import net.minecraft.entity.passive.AbstractHorseEntity @@ -15,48 +15,45 @@ import net.minecraft.item.Items import moe.nea.firmament.gui.entity.EntityRenderer.fakeWorld object ModifyHorse : EntityModifier { - override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { - require(entity is AbstractHorseEntity) - var entity: AbstractHorseEntity = entity - info["kind"]?.let { - entity = when (it.asString) { - "skeleton" -> EntityType.SKELETON_HORSE.create(fakeWorld, SpawnReason.LOAD)!! - "zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld, SpawnReason.LOAD)!! - "mule" -> EntityType.MULE.create(fakeWorld, SpawnReason.LOAD)!! - "donkey" -> EntityType.DONKEY.create(fakeWorld, SpawnReason.LOAD)!! - "horse" -> EntityType.HORSE.create(fakeWorld, SpawnReason.LOAD)!! - else -> error("Unknown horse kind $it") - } - } - info["armor"]?.let { - if (it is JsonNull) { - entity.setHorseArmor(ItemStack.EMPTY) - } else { - when (it.asString) { - "iron" -> entity.setHorseArmor(ItemStack(Items.IRON_HORSE_ARMOR)) - "golden" -> entity.setHorseArmor(ItemStack(Items.GOLDEN_HORSE_ARMOR)) - "diamond" -> entity.setHorseArmor(ItemStack(Items.DIAMOND_HORSE_ARMOR)) - else -> error("Unknown horse armor $it") - } - } - } - info["saddled"]?.let { - entity.setIsSaddled(it.asBoolean) - } - return entity - } + override fun apply(entity: LivingEntity, info: JsonObject): LivingEntity { + require(entity is AbstractHorseEntity) + var entity: AbstractHorseEntity = entity + info["kind"]?.let { + entity = when (it.asString) { + "skeleton" -> EntityType.SKELETON_HORSE.create(fakeWorld, SpawnReason.LOAD)!! + "zombie" -> EntityType.ZOMBIE_HORSE.create(fakeWorld, SpawnReason.LOAD)!! + "mule" -> EntityType.MULE.create(fakeWorld, SpawnReason.LOAD)!! + "donkey" -> EntityType.DONKEY.create(fakeWorld, SpawnReason.LOAD)!! + "horse" -> EntityType.HORSE.create(fakeWorld, SpawnReason.LOAD)!! + else -> error("Unknown horse kind $it") + } + } + info["armor"]?.let { + if (it is JsonNull) { + entity.setHorseArmor(ItemStack.EMPTY) + } else { + when (it.asString) { + "iron" -> entity.setHorseArmor(ItemStack(Items.IRON_HORSE_ARMOR)) + "golden" -> entity.setHorseArmor(ItemStack(Items.GOLDEN_HORSE_ARMOR)) + "diamond" -> entity.setHorseArmor(ItemStack(Items.DIAMOND_HORSE_ARMOR)) + else -> error("Unknown horse armor $it") + } + } + } + info["saddled"]?.let { + entity.setIsSaddled(it.asBoolean) + } + return entity + } } fun AbstractHorseEntity.setIsSaddled(shouldBeSaddled: Boolean) { - val oldFlag = dataTracker.get(AbstractHorseEntity.HORSE_FLAGS) - dataTracker.set( - AbstractHorseEntity.HORSE_FLAGS, - if (shouldBeSaddled) oldFlag or AbstractHorseEntity.SADDLED_FLAG.toByte() - else oldFlag and AbstractHorseEntity.SADDLED_FLAG.toByte().inv() - ) + this.equipStack(EquipmentSlot.SADDLE, + if (shouldBeSaddled) ItemStack(Items.SADDLE) + else ItemStack.EMPTY) } fun AbstractHorseEntity.setHorseArmor(itemStack: ItemStack) { - items.setStack(1, itemStack) + this.equipBodyArmor(itemStack) } diff --git a/src/main/kotlin/repo/ExpLadder.kt b/src/main/kotlin/repo/ExpLadder.kt index fbc9eb8..25a74de 100644 --- a/src/main/kotlin/repo/ExpLadder.kt +++ b/src/main/kotlin/repo/ExpLadder.kt @@ -19,7 +19,8 @@ object ExpLadders : IReloadable { val expInCurrentLevel: Float, var expTotal: Float, ) { - val percentageToNextLevel: Float = expInCurrentLevel / expRequiredForNextLevel + val percentageToNextLevel: Float = expInCurrentLevel / expRequiredForNextLevel + val percentageToMaxLevel: Float = expTotal / expRequiredForMaxLevel } data class ExpLadder( diff --git a/src/main/kotlin/repo/ItemCache.kt b/src/main/kotlin/repo/ItemCache.kt index 0967ad1..09eedac 100644 --- a/src/main/kotlin/repo/ItemCache.kt +++ b/src/main/kotlin/repo/ItemCache.kt @@ -4,7 +4,6 @@ import com.mojang.serialization.Dynamic import io.github.moulberry.repo.IReloadable import io.github.moulberry.repo.NEURepository import io.github.moulberry.repo.data.NEUItem -import io.github.notenoughupdates.moulconfig.xml.Bind import java.text.NumberFormat import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -13,7 +12,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlin.jvm.optionals.getOrNull import net.minecraft.SharedConstants -import net.minecraft.client.resource.language.I18n import net.minecraft.component.DataComponentTypes import net.minecraft.component.type.NbtComponent import net.minecraft.datafixer.Schemas @@ -28,9 +26,6 @@ import net.minecraft.text.MutableText import net.minecraft.text.Style import net.minecraft.text.Text import moe.nea.firmament.Firmament -import moe.nea.firmament.gui.config.HudMeta -import moe.nea.firmament.gui.config.HudPosition -import moe.nea.firmament.gui.hud.MoulConfigHud import moe.nea.firmament.repo.RepoManager.initialize import moe.nea.firmament.util.LegacyFormattingCode import moe.nea.firmament.util.LegacyTagParser @@ -140,7 +135,8 @@ object ItemCache : IReloadable { ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this) itemInstance.loreAccordingToNbt = lore.map { un189Lore(it) } itemInstance.displayNameAccordingToNbt = un189Lore(displayName) - val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes") + val extraAttributes = oldItemTag.getCompound("tag").flatMap { it.getCompound("ExtraAttributes") } + .getOrNull() if (extraAttributes != null) itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes)) return itemInstance diff --git a/src/main/kotlin/repo/RepoModResourcePack.kt b/src/main/kotlin/repo/RepoModResourcePack.kt index 2fdf710..4fec14a 100644 --- a/src/main/kotlin/repo/RepoModResourcePack.kt +++ b/src/main/kotlin/repo/RepoModResourcePack.kt @@ -24,7 +24,7 @@ import net.minecraft.resource.metadata.ResourceMetadata import net.minecraft.resource.metadata.ResourceMetadataSerializer import net.minecraft.text.Text import net.minecraft.util.Identifier -import net.minecraft.util.PathUtil +import net.minecraft.util.path.PathUtil import moe.nea.firmament.Firmament class RepoModResourcePack(val basePath: Path) : ModResourcePack { diff --git a/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt b/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt index 9a1aea5..3774f26 100644 --- a/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt +++ b/src/main/kotlin/repo/recipes/GenericRecipeRenderer.kt @@ -9,11 +9,13 @@ import net.minecraft.util.Identifier import moe.nea.firmament.repo.SBItemStack interface GenericRecipeRenderer<T : NEURecipe> { - fun render(recipe: T, bounds: Rectangle, layouter: RecipeLayouter) + fun render(recipe: T, bounds: Rectangle, layouter: RecipeLayouter, mainItem: SBItemStack?) fun getInputs(recipe: T): Collection<SBItemStack> fun getOutputs(recipe: T): Collection<SBItemStack> val icon: ItemStack val title: Text val identifier: Identifier fun findAllRecipes(neuRepository: NEURepository): Iterable<T> + val displayHeight: Int get() = 66 + val typ: Class<T> } diff --git a/src/main/kotlin/repo/recipes/RecipeLayouter.kt b/src/main/kotlin/repo/recipes/RecipeLayouter.kt index 109bff5..ed0dca2 100644 --- a/src/main/kotlin/repo/recipes/RecipeLayouter.kt +++ b/src/main/kotlin/repo/recipes/RecipeLayouter.kt @@ -1,6 +1,8 @@ package moe.nea.firmament.repo.recipes import io.github.notenoughupdates.moulconfig.gui.GuiComponent +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle import net.minecraft.text.Text import moe.nea.firmament.repo.SBItemStack @@ -21,13 +23,16 @@ interface RecipeLayouter { slotKind: SlotKind, ) + fun createTooltip(rectangle: Rectangle, label: Text) + fun createLabel( x: Int, y: Int, text: Text ) - fun createArrow(x: Int, y: Int) + fun createArrow(x: Int, y: Int): Rectangle fun createMoulConfig(x: Int, y: Int, w: Int, h: Int, component: GuiComponent) + fun createFire(ingredientsCenter: Point, animationTicks: Int) } diff --git a/src/main/kotlin/repo/recipes/SBCraftingRecipeRenderer.kt b/src/main/kotlin/repo/recipes/SBCraftingRecipeRenderer.kt index 679aec8..e38380c 100644 --- a/src/main/kotlin/repo/recipes/SBCraftingRecipeRenderer.kt +++ b/src/main/kotlin/repo/recipes/SBCraftingRecipeRenderer.kt @@ -12,17 +12,24 @@ import moe.nea.firmament.Firmament import moe.nea.firmament.repo.SBItemStack import moe.nea.firmament.util.tr -class SBCraftingRecipeRenderer : GenericRecipeRenderer<NEUCraftingRecipe> { - override fun render(recipe: NEUCraftingRecipe, bounds: Rectangle, layouter: RecipeLayouter) { +object SBCraftingRecipeRenderer : GenericRecipeRenderer<NEUCraftingRecipe> { + override fun render( + recipe: NEUCraftingRecipe, + bounds: Rectangle, + layouter: RecipeLayouter, + mainItem: SBItemStack?, + ) { val point = Point(bounds.centerX - 58, bounds.centerY - 27) layouter.createArrow(point.x + 60, point.y + 18) for (i in 0 until 3) { for (j in 0 until 3) { val item = recipe.inputs[i + j * 3] - layouter.createItemSlot(point.x + 1 + i * 18, - point.y + 1 + j * 18, - SBItemStack(item), - RecipeLayouter.SlotKind.SMALL_INPUT) + layouter.createItemSlot( + point.x + 1 + i * 18, + point.y + 1 + j * 18, + SBItemStack(item), + RecipeLayouter.SlotKind.SMALL_INPUT + ) } } layouter.createItemSlot( @@ -32,6 +39,9 @@ class SBCraftingRecipeRenderer : GenericRecipeRenderer<NEUCraftingRecipe> { ) } + override val typ: Class<NEUCraftingRecipe> + get() = NEUCraftingRecipe::class.java + override fun getInputs(recipe: NEUCraftingRecipe): Collection<SBItemStack> { return recipe.allInputs.mapNotNull { SBItemStack(it) } } @@ -45,6 +55,6 @@ class SBCraftingRecipeRenderer : GenericRecipeRenderer<NEUCraftingRecipe> { } override val icon: ItemStack = ItemStack(Blocks.CRAFTING_TABLE) - override val title: Text = tr("firmament.category.crafting", "SkyBlock Crafting") // TODO: fix tr not being included in jars + override val title: Text = tr("firmament.category.crafting", "SkyBlock Crafting") override val identifier: Identifier = Firmament.identifier("crafting_recipe") } diff --git a/src/main/kotlin/repo/recipes/SBEssenceUpgradeRecipeRenderer.kt b/src/main/kotlin/repo/recipes/SBEssenceUpgradeRecipeRenderer.kt new file mode 100644 index 0000000..0f5271f --- /dev/null +++ b/src/main/kotlin/repo/recipes/SBEssenceUpgradeRecipeRenderer.kt @@ -0,0 +1,74 @@ +package moe.nea.firmament.repo.recipes + +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUForgeRecipe +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import net.minecraft.item.ItemStack +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import moe.nea.firmament.Firmament +import moe.nea.firmament.repo.EssenceRecipeProvider +import moe.nea.firmament.repo.RepoManager +import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.tr + +object SBEssenceUpgradeRecipeRenderer : GenericRecipeRenderer<EssenceRecipeProvider.EssenceUpgradeRecipe> { + override fun render( + recipe: EssenceRecipeProvider.EssenceUpgradeRecipe, + bounds: Rectangle, + layouter: RecipeLayouter, + mainItem: SBItemStack? + ) { + val sourceItem = mainItem ?: SBItemStack(recipe.itemId) + layouter.createItemSlot( + bounds.minX + 12, + bounds.centerY - 8 - 18 / 2, + sourceItem.copy(stars = recipe.starCountAfter - 1), + RecipeLayouter.SlotKind.SMALL_INPUT + ) + layouter.createItemSlot( + bounds.minX + 12, bounds.centerY - 8 + 18 / 2, + SBItemStack(recipe.essenceIngredient), + RecipeLayouter.SlotKind.SMALL_INPUT + ) + layouter.createItemSlot( + bounds.maxX - 12 - 16, bounds.centerY - 8, + sourceItem.copy(stars = recipe.starCountAfter), + RecipeLayouter.SlotKind.SMALL_OUTPUT + ) + val extraItems = recipe.extraItems + layouter.createArrow( + bounds.centerX - 24 / 2, + if (extraItems.isEmpty()) bounds.centerY - 17 / 2 + else bounds.centerY + 18 / 2 + ) + for ((index, item) in extraItems.withIndex()) { + layouter.createItemSlot( + bounds.centerX - extraItems.size * 16 / 2 - 2 / 2 + index * 18, + bounds.centerY - 18 / 2, + SBItemStack(item), + RecipeLayouter.SlotKind.SMALL_INPUT, + ) + } + } + + override fun getInputs(recipe: EssenceRecipeProvider.EssenceUpgradeRecipe): Collection<SBItemStack> { + return recipe.allInputs.mapNotNull { SBItemStack(it) } + } + + override fun getOutputs(recipe: EssenceRecipeProvider.EssenceUpgradeRecipe): Collection<SBItemStack> { + return listOfNotNull(SBItemStack(recipe.itemId)) + } + + override val icon: ItemStack get() = SBItemStack(SkyblockId("ESSENCE_WITHER")).asImmutableItemStack() + override val title: Text = tr("firmament.category.essence", "Essence Upgrades") + override val identifier: Identifier = Firmament.identifier("essence_upgrade") + override fun findAllRecipes(neuRepository: NEURepository): Iterable<EssenceRecipeProvider.EssenceUpgradeRecipe> { + return RepoManager.essenceRecipeProvider.recipes + } + + override val typ: Class<EssenceRecipeProvider.EssenceUpgradeRecipe> + get() = EssenceRecipeProvider.EssenceUpgradeRecipe::class.java +} diff --git a/src/main/kotlin/repo/recipes/SBForgeRecipeRenderer.kt b/src/main/kotlin/repo/recipes/SBForgeRecipeRenderer.kt new file mode 100644 index 0000000..9fdb756 --- /dev/null +++ b/src/main/kotlin/repo/recipes/SBForgeRecipeRenderer.kt @@ -0,0 +1,83 @@ +package moe.nea.firmament.repo.recipes + +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUCraftingRecipe +import io.github.moulberry.repo.data.NEUForgeRecipe +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import kotlin.math.cos +import kotlin.math.sin +import kotlin.time.Duration.Companion.seconds +import net.minecraft.block.Blocks +import net.minecraft.item.ItemStack +import net.minecraft.text.Text +import net.minecraft.util.Identifier +import moe.nea.firmament.Firmament +import moe.nea.firmament.repo.SBItemStack +import moe.nea.firmament.util.tr + +object SBForgeRecipeRenderer : GenericRecipeRenderer<NEUForgeRecipe> { + override fun render( + recipe: NEUForgeRecipe, + bounds: Rectangle, + layouter: RecipeLayouter, + mainItem: SBItemStack?, + ) { + val arrow = layouter.createArrow(bounds.minX + 90, bounds.minY + 54 - 18 / 2) + layouter.createTooltip( + arrow, + Text.stringifiedTranslatable( + "firmament.recipe.forge.time", + recipe.duration.seconds + ) + ) + + val ingredientsCenter = Point(bounds.minX + 49 - 8, bounds.minY + 54 - 8) + layouter.createFire(ingredientsCenter, 25) + val count = recipe.inputs.size + if (count == 1) { + layouter.createItemSlot( + ingredientsCenter.x, ingredientsCenter.y - 18, + SBItemStack(recipe.inputs.single()), + RecipeLayouter.SlotKind.SMALL_INPUT, + ) + } else { + recipe.inputs.forEachIndexed { idx, ingredient -> + val rad = Math.PI * 2 * idx / count + layouter.createItemSlot( + (ingredientsCenter.x + cos(rad) * 30).toInt(), (ingredientsCenter.y + sin(rad) * 30).toInt(), + SBItemStack(ingredient), + RecipeLayouter.SlotKind.SMALL_INPUT, + ) + } + } + layouter.createItemSlot( + bounds.minX + 124, bounds.minY + 46, + SBItemStack(recipe.outputStack), + RecipeLayouter.SlotKind.BIG_OUTPUT + ) + } + + override val displayHeight: Int + get() = 104 + + override fun getInputs(recipe: NEUForgeRecipe): Collection<SBItemStack> { + return recipe.inputs.mapNotNull { SBItemStack(it) } + } + + override fun getOutputs(recipe: NEUForgeRecipe): Collection<SBItemStack> { + return listOfNotNull(SBItemStack(recipe.outputStack)) + } + + override val icon: ItemStack = ItemStack(Blocks.ANVIL) + override val title: Text = tr("firmament.category.forge", "Forge Recipes") + override val identifier: Identifier = Firmament.identifier("forge_recipe") + + override fun findAllRecipes(neuRepository: NEURepository): Iterable<NEUForgeRecipe> { + // TODO: theres gotta be an index for these tbh. + return neuRepository.items.items.values.flatMap { it.recipes }.filterIsInstance<NEUForgeRecipe>() + } + + override val typ: Class<NEUForgeRecipe> + get() = NEUForgeRecipe::class.java +} diff --git a/src/main/kotlin/util/Base64Util.kt b/src/main/kotlin/util/Base64Util.kt index 44bcdfd..c39c601 100644 --- a/src/main/kotlin/util/Base64Util.kt +++ b/src/main/kotlin/util/Base64Util.kt @@ -1,7 +1,14 @@ package moe.nea.firmament.util +import java.util.Base64 + object Base64Util { + fun decodeString(str: String): String { + return Base64.getDecoder().decode(str.padToValidBase64()) + .decodeToString() + } + fun String.padToValidBase64(): String { val align = this.length % 4 if (align == 0) return this diff --git a/src/main/kotlin/util/ErrorUtil.kt b/src/main/kotlin/util/ErrorUtil.kt index 190381d..af36d81 100644 --- a/src/main/kotlin/util/ErrorUtil.kt +++ b/src/main/kotlin/util/ErrorUtil.kt @@ -38,6 +38,8 @@ object ErrorUtil { } class Catch<T> private constructor(val value: T?, val exc: Throwable?) { + fun orNull(): T? = value + inline fun or(block: (exc: Throwable) -> T): T { contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) diff --git a/src/main/kotlin/util/FirmFormatters.kt b/src/main/kotlin/util/FirmFormatters.kt index a660f51..03dafc5 100644 --- a/src/main/kotlin/util/FirmFormatters.kt +++ b/src/main/kotlin/util/FirmFormatters.kt @@ -135,4 +135,8 @@ object FirmFormatters { fun formatPosition(position: BlockPos): Text { return Text.literal("x: ${position.x}, y: ${position.y}, z: ${position.z}") } + + fun formatPercent(value: Double, decimals: Int = 1): String { + return "%.${decimals}f%%".format(value * 100) + } } diff --git a/src/main/kotlin/util/MC.kt b/src/main/kotlin/util/MC.kt index c1a5e65..d8a3ab1 100644 --- a/src/main/kotlin/util/MC.kt +++ b/src/main/kotlin/util/MC.kt @@ -2,6 +2,7 @@ package moe.nea.firmament.util import io.github.moulberry.repo.data.Coordinate import java.util.concurrent.ConcurrentLinkedQueue +import kotlin.jvm.optionals.getOrNull import net.minecraft.client.MinecraftClient import net.minecraft.client.gui.hud.InGameHud import net.minecraft.client.gui.screen.Screen @@ -16,10 +17,14 @@ import net.minecraft.item.Item import net.minecraft.item.ItemStack import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket import net.minecraft.registry.BuiltinRegistries +import net.minecraft.registry.Registry +import net.minecraft.registry.RegistryKey import net.minecraft.registry.RegistryKeys import net.minecraft.registry.RegistryWrapper import net.minecraft.resource.ReloadableResourceManagerImpl import net.minecraft.text.Text +import net.minecraft.util.Identifier +import net.minecraft.util.Util import net.minecraft.util.math.BlockPos import net.minecraft.world.World import moe.nea.firmament.events.TickEvent @@ -99,7 +104,7 @@ object MC { inline val soundManager get() = instance.soundManager inline val player: ClientPlayerEntity? get() = TestUtil.unlessTesting { instance.player } inline val camera: Entity? get() = instance.cameraEntity - inline val stackInHand: ItemStack get() = player?.inventory?.mainHandStack ?: ItemStack.EMPTY + inline val stackInHand: ItemStack get() = player?.mainHandStack ?: ItemStack.EMPTY inline val guiAtlasManager get() = instance.guiAtlasManager inline val world: ClientWorld? get() = TestUtil.unlessTesting { instance.world } inline val playerName: String? get() = player?.name?.unformattedString @@ -120,6 +125,23 @@ object MC { return field } private set + + + fun openUrl(uri: String) { + Util.getOperatingSystem().open(uri) + } + + fun <T> unsafeGetRegistryEntry(registry: RegistryKey<out Registry<T>>, identifier: Identifier) = + unsafeGetRegistryEntry(RegistryKey.of(registry, identifier)) + + + fun <T> unsafeGetRegistryEntry(registryKey: RegistryKey<T>): T? { + return currentOrDefaultRegistries + .getOrThrow(registryKey.registryRef) + .getOptional(registryKey) + .getOrNull() + ?.value() + } } diff --git a/src/main/kotlin/util/MoulConfigUtils.kt b/src/main/kotlin/util/MoulConfigUtils.kt index 362a4d9..a9e3241 100644 --- a/src/main/kotlin/util/MoulConfigUtils.kt +++ b/src/main/kotlin/util/MoulConfigUtils.kt @@ -9,6 +9,7 @@ import io.github.notenoughupdates.moulconfig.gui.GuiContext import io.github.notenoughupdates.moulconfig.gui.GuiImmediateContext import io.github.notenoughupdates.moulconfig.gui.KeyboardEvent import io.github.notenoughupdates.moulconfig.gui.MouseEvent +import io.github.notenoughupdates.moulconfig.gui.component.PanelComponent import io.github.notenoughupdates.moulconfig.observer.GetSetter import io.github.notenoughupdates.moulconfig.platform.ModernRenderContext import io.github.notenoughupdates.moulconfig.xml.ChildCount @@ -20,6 +21,7 @@ import java.io.File import java.util.function.Supplier import javax.xml.namespace.QName import me.shedaniel.math.Color +import org.jetbrains.annotations.Unmodifiable import org.w3c.dom.Element import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -35,6 +37,19 @@ import moe.nea.firmament.gui.TickComponent import moe.nea.firmament.util.render.isUntranslatedGuiDrawContext object MoulConfigUtils { + @JvmStatic + fun main(args: Array<out String>) { + generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS) + generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl) + File("wrapper.xsd").writeText(""" +<?xml version="1.0" encoding="UTF-8" ?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:import namespace="http://notenoughupdates.org/moulconfig" schemaLocation="MoulConfig.xsd"/> + <xs:import namespace="http://firmament.nea.moe/moulconfig" schemaLocation="MoulConfig.Firmament.xsd"/> +</xs:schema> + """.trimIndent()) + } + val firmUrl = "http://firmament.nea.moe/moulconfig" val universe = XMLUniverse.getDefaultUniverse().also { uni -> uni.registerMapper(java.awt.Color::class.java) { @@ -179,10 +194,8 @@ object MoulConfigUtils { uni.registerLoader(object : XMLGuiLoader.Basic<FixedComponent> { override fun createInstance(context: XMLContext<*>, element: Element): FixedComponent { return FixedComponent( - context.getPropertyFromAttribute(element, QName("width"), Int::class.java) - ?: error("Requires width specified"), - context.getPropertyFromAttribute(element, QName("height"), Int::class.java) - ?: error("Requires height specified"), + context.getPropertyFromAttribute(element, QName("width"), Int::class.java), + context.getPropertyFromAttribute(element, QName("height"), Int::class.java), context.getChildFragment(element) ) } @@ -196,7 +209,7 @@ object MoulConfigUtils { } override fun getAttributeNames(): Map<String, Boolean> { - return mapOf("width" to true, "height" to true) + return mapOf("width" to false, "height" to false) } }) } @@ -210,19 +223,6 @@ object MoulConfigUtils { generator.dumpToFile(file) } - @JvmStatic - fun main(args: Array<out String>) { - generateXSD(File("MoulConfig.xsd"), XMLUniverse.MOULCONFIG_XML_NS) - generateXSD(File("MoulConfig.Firmament.xsd"), firmUrl) - File("wrapper.xsd").writeText(""" -<?xml version="1.0" encoding="UTF-8" ?> -<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> - <xs:import namespace="http://notenoughupdates.org/moulconfig" schemaLocation="MoulConfig.xsd"/> - <xs:import namespace="http://firmament.nea.moe/moulconfig" schemaLocation="MoulConfig.Firmament.xsd"/> -</xs:schema> - """.trimIndent()) - } - fun loadScreen(name: String, bindTo: Any, parent: Screen?): Screen { return object : GuiComponentWrapper(loadGui(name, bindTo)) { override fun close() { diff --git a/src/main/kotlin/util/SkyblockId.kt b/src/main/kotlin/util/SkyblockId.kt index a31255c..42d9a89 100644 --- a/src/main/kotlin/util/SkyblockId.kt +++ b/src/main/kotlin/util/SkyblockId.kt @@ -21,6 +21,7 @@ import net.minecraft.network.RegistryByteBuf import net.minecraft.network.codec.PacketCodec import net.minecraft.network.codec.PacketCodecs import net.minecraft.util.Identifier +import moe.nea.firmament.repo.ExpLadders import moe.nea.firmament.repo.ItemCache.asItemStack import moe.nea.firmament.repo.set import moe.nea.firmament.util.collections.WeakCache @@ -104,8 +105,10 @@ data class HypixelPetInfo( val candyUsed: Int = 0, val uuid: UUID? = null, val active: Boolean = false, + val heldItem: String? = null, ) { val skyblockId get() = SkyblockId("${type.uppercase()};${tier.ordinal}") // TODO: is this ordinal set up correctly? + val level get() = ExpLadders.getExpLadder(type, tier).getPetLevel(exp) } private val jsonparser = Json { ignoreUnknownKeys = true } @@ -130,13 +133,14 @@ fun ItemStack.modifyExtraAttributes(block: (NbtCompound) -> Unit) { } val ItemStack.skyblockUUIDString: String? - get() = extraAttributes.getString("uuid")?.takeIf { it.isNotBlank() } + get() = extraAttributes.getString("uuid").getOrNull()?.takeIf { it.isNotBlank() } val ItemStack.skyblockUUID: UUID? get() = skyblockUUIDString?.let { UUID.fromString(it) } private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo>>("PetInfo") { val jsonString = it.extraAttributes.getString("petInfo") + .getOrNull() if (jsonString.isNullOrBlank()) return@memoize Optional.empty() ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") { jsonparser.decodeFromString<HypixelPetInfo>(jsonString) @@ -145,8 +149,8 @@ private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo> } fun ItemStack.getUpgradeStars(): Int { - return extraAttributes.getInt("upgrade_level").takeIf { it > 0 } - ?: extraAttributes.getInt("dungeon_item_level").takeIf { it > 0 } + return extraAttributes.getInt("upgrade_level").getOrNull()?.takeIf { it > 0 } + ?: extraAttributes.getInt("dungeon_item_level").getOrNull()?.takeIf { it > 0 } ?: 0 } @@ -155,7 +159,7 @@ fun ItemStack.getUpgradeStars(): Int { value class ReforgeId(val id: String) fun ItemStack.getReforgeId(): ReforgeId? { - return extraAttributes.getString("modifier").takeIf { it.isNotBlank() }?.let(::ReforgeId) + return extraAttributes.getString("modifier").getOrNull()?.takeIf { it.isNotBlank() }?.let(::ReforgeId) } val ItemStack.petData: HypixelPetInfo? @@ -169,8 +173,8 @@ fun ItemStack.setSkyBlockId(skyblockId: SkyblockId): ItemStack { val ItemStack.skyBlockId: SkyblockId? get() { - return when (val id = extraAttributes.getString("id")) { - "" -> { + return when (val id = extraAttributes.getString("id").getOrNull()) { + "", null -> { null } @@ -180,20 +184,22 @@ val ItemStack.skyBlockId: SkyblockId? "RUNE", "UNIQUE_RUNE" -> { val runeData = extraAttributes.getCompound("runes") - val runeKind = runeData.keys.singleOrNull() + .getOrNull() + val runeKind = runeData?.keys?.singleOrNull() if (runeKind == null) SkyblockId("RUNE") - else SkyblockId("${runeKind.uppercase()}_RUNE;${runeData.getInt(runeKind)}") + else SkyblockId("${runeKind.uppercase()}_RUNE;${runeData.getInt(runeKind).getOrNull()}") } "ABICASE" -> { - SkyblockId("ABICASE_${extraAttributes.getString("model").uppercase()}") + SkyblockId("ABICASE_${extraAttributes.getString("model").getOrNull()?.uppercase()}") } "ENCHANTED_BOOK" -> { val enchantmentData = extraAttributes.getCompound("enchantments") - val enchantName = enchantmentData.keys.singleOrNull() + .getOrNull() + val enchantName = enchantmentData?.keys?.singleOrNull() if (enchantName == null) SkyblockId("ENCHANTED_BOOK") - else SkyblockId("${enchantName.uppercase()};${enchantmentData.getInt(enchantName)}") + else SkyblockId("${enchantName.uppercase()};${enchantmentData.getInt(enchantName).getOrNull()}") } // TODO: PARTY_HAT_CRAB{,_ANIMATED,_SLOTH},POTION diff --git a/src/main/kotlin/util/asm/AsmAnnotationUtil.kt b/src/main/kotlin/util/asm/AsmAnnotationUtil.kt new file mode 100644 index 0000000..fb0e92c --- /dev/null +++ b/src/main/kotlin/util/asm/AsmAnnotationUtil.kt @@ -0,0 +1,89 @@ +package moe.nea.firmament.util.asm + +import com.google.common.base.Defaults +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method +import java.lang.reflect.Proxy +import org.objectweb.asm.Type +import org.objectweb.asm.tree.AnnotationNode + +object AsmAnnotationUtil { + class AnnotationProxy( + val originalType: Class<out Annotation>, + val annotationNode: AnnotationNode, + ) : InvocationHandler { + val offsets = annotationNode.values.withIndex() + .chunked(2) + .map { it.first() } + .associate { (idx, value) -> value as String to idx + 1 } + + fun nestArrayType(depth: Int, comp: Class<*>): Class<*> = + if (depth == 0) comp + else java.lang.reflect.Array.newInstance(nestArrayType(depth - 1, comp), 0).javaClass + + fun unmap( + value: Any?, + comp: Class<*>, + depth: Int, + ): Any? { + value ?: return null + if (depth > 0) + return ((value as List<Any>) + .map { unmap(it, comp, depth - 1) } as java.util.List<Any>) + .toArray(java.lang.reflect.Array.newInstance(nestArrayType(depth - 1, comp), 0) as Array<*>) + if (comp.isEnum) { + comp as Class<out Enum<*>> + when (value) { + is String -> return java.lang.Enum.valueOf(comp, value) + is List<*> -> return java.lang.Enum.valueOf(comp, value[1] as String) + else -> error("Unknown enum variant $value for $comp") + } + } + when (value) { + is Type -> return Class.forName(value.className) + is AnnotationNode -> return createProxy(comp as Class<out Annotation>, value) + is String, is Boolean, is Byte, is Double, is Int, is Float, is Long, is Short, is Char -> return value + } + error("Unknown enum variant $value for $comp") + } + + fun defaultFor(fullType: Class<*>): Any? { + if (fullType.isArray) return java.lang.reflect.Array.newInstance(fullType.componentType, 0) + if (fullType.isPrimitive) { + return Defaults.defaultValue(fullType) + } + if (fullType == String::class.java) + return "" + return null + } + + override fun invoke( + proxy: Any, + method: Method, + args: Array<out Any?>? + ): Any? { + val name = method.name + val ret = method.returnType + val retU = generateSequence(ret) { if (it.isArray) it.componentType else null } + .toList() + val arrayDepth = retU.size - 1 + val componentType = retU.last() + + val off = offsets[name] + if (off == null) { + return defaultFor(ret) + } + return unmap(annotationNode.values[off], componentType, arrayDepth) + } + } + + fun <T : Annotation> createProxy( + annotationClass: Class<T>, + annotationNode: AnnotationNode + ): T { + require(Type.getType(annotationClass) == Type.getType(annotationNode.desc)) + return Proxy.newProxyInstance(javaClass.classLoader, + arrayOf(annotationClass), + AnnotationProxy(annotationClass, annotationNode)) as T + } +} diff --git a/src/main/kotlin/util/collections/WeakCache.kt b/src/main/kotlin/util/collections/WeakCache.kt index 38f9886..4a48c63 100644 --- a/src/main/kotlin/util/collections/WeakCache.kt +++ b/src/main/kotlin/util/collections/WeakCache.kt @@ -9,102 +9,108 @@ import moe.nea.firmament.features.debug.DebugLogger * the key. Each key can have additional extra data that is used to look up values. That extra data is not required to * be a life reference. The main Key is compared using strict reference equality. This map is not synchronized. */ -class WeakCache<Key : Any, ExtraKey : Any, Value : Any>(val name: String) { - private val queue = object : ReferenceQueue<Key>() {} - private val map = mutableMapOf<Ref, Value>() - - val size: Int - get() { - clearOldReferences() - return map.size - } - - fun clearOldReferences() { - var successCount = 0 - var totalCount = 0 - while (true) { - val reference = queue.poll() ?: break - totalCount++ - if (map.remove(reference) != null) - successCount++ - } - if (totalCount > 0) - logger.log { "Cleared $successCount/$totalCount references from queue" } - } - - fun get(key: Key, extraData: ExtraKey): Value? { - clearOldReferences() - return map[Ref(key, extraData)] - } - - fun put(key: Key, extraData: ExtraKey, value: Value) { - clearOldReferences() - map[Ref(key, extraData)] = value - } - - fun getOrPut(key: Key, extraData: ExtraKey, value: (Key, ExtraKey) -> Value): Value { - clearOldReferences() - return map.getOrPut(Ref(key, extraData)) { value(key, extraData) } - } - - fun clear() { - map.clear() - } - - init { - allInstances.add(this) - } - - companion object { - val allInstances = InstanceList<WeakCache<*, *, *>>("WeakCaches") - private val logger = DebugLogger("WeakCache") - fun <Key : Any, Value : Any> memoize(name: String, function: (Key) -> Value): - CacheFunction.NoExtraData<Key, Value> { - return CacheFunction.NoExtraData(WeakCache(name), function) - } - - fun <Key : Any, ExtraKey : Any, Value : Any> memoize(name: String, function: (Key, ExtraKey) -> Value): - CacheFunction.WithExtraData<Key, ExtraKey, Value> { - return CacheFunction.WithExtraData(WeakCache(name), function) - } - } - - inner class Ref( - weakInstance: Key, - val extraData: ExtraKey, - ) : WeakReference<Key>(weakInstance, queue) { - val hashCode = System.identityHashCode(weakInstance) * 31 + extraData.hashCode() - override fun equals(other: Any?): Boolean { - if (other !is WeakCache<*, *, *>.Ref) return false - return other.hashCode == this.hashCode - && other.get() === this.get() - && other.extraData == this.extraData - } - - override fun hashCode(): Int { - return hashCode - } - } - - interface CacheFunction { - val cache: WeakCache<*, *, *> - - data class NoExtraData<Key : Any, Value : Any>( - override val cache: WeakCache<Key, Unit, Value>, - val wrapped: (Key) -> Value, - ) : CacheFunction, (Key) -> Value { - override fun invoke(p1: Key): Value { - return cache.getOrPut(p1, Unit, { a, _ -> wrapped(a) }) - } - } - - data class WithExtraData<Key : Any, ExtraKey : Any, Value : Any>( - override val cache: WeakCache<Key, ExtraKey, Value>, - val wrapped: (Key, ExtraKey) -> Value, - ) : CacheFunction, (Key, ExtraKey) -> Value { - override fun invoke(p1: Key, p2: ExtraKey): Value { - return cache.getOrPut(p1, p2, wrapped) - } - } - } +open class WeakCache<Key : Any, ExtraKey : Any, Value : Any>(val name: String) { + private val queue = object : ReferenceQueue<Key>() {} + private val map = mutableMapOf<Ref, Value>() + + val size: Int + get() { + clearOldReferences() + return map.size + } + + fun clearOldReferences() { + var successCount = 0 + var totalCount = 0 + while (true) { + val reference = queue.poll() as WeakCache<*, *, *>.Ref? ?: break + totalCount++ + if (reference.shouldBeEvicted() && map.remove(reference) != null) + successCount++ + } + if (totalCount > 0) + logger.log("Cleared $successCount/$totalCount references from queue") + } + + open fun mkRef(key: Key, extraData: ExtraKey): Ref { + return Ref(key, extraData) + } + + fun get(key: Key, extraData: ExtraKey): Value? { + clearOldReferences() + return map[mkRef(key, extraData)] + } + + fun put(key: Key, extraData: ExtraKey, value: Value) { + clearOldReferences() + map[mkRef(key, extraData)] = value + } + + fun getOrPut(key: Key, extraData: ExtraKey, value: (Key, ExtraKey) -> Value): Value { + clearOldReferences() + return map.getOrPut(mkRef(key, extraData)) { value(key, extraData) } + } + + fun clear() { + map.clear() + } + + init { + allInstances.add(this) + } + + companion object { + val allInstances = InstanceList<WeakCache<*, *, *>>("WeakCaches") + private val logger = DebugLogger("WeakCache") + fun <Key : Any, Value : Any> memoize(name: String, function: (Key) -> Value): + CacheFunction.NoExtraData<Key, Value> { + return CacheFunction.NoExtraData(WeakCache(name), function) + } + + fun <Key : Any, ExtraKey : Any, Value : Any> dontMemoize(name: String, function: (Key, ExtraKey) -> Value) = function + fun <Key : Any, ExtraKey : Any, Value : Any> memoize(name: String, function: (Key, ExtraKey) -> Value): + CacheFunction.WithExtraData<Key, ExtraKey, Value> { + return CacheFunction.WithExtraData(WeakCache(name), function) + } + } + + open inner class Ref( + weakInstance: Key, + val extraData: ExtraKey, + ) : WeakReference<Key>(weakInstance, queue) { + open fun shouldBeEvicted() = true + val hashCode = System.identityHashCode(weakInstance) * 31 + extraData.hashCode() + override fun equals(other: Any?): Boolean { + if (other !is WeakCache<*, *, *>.Ref) return false + return other.hashCode == this.hashCode + && other.get() === this.get() + && other.extraData == this.extraData + } + + override fun hashCode(): Int { + return hashCode + } + } + + interface CacheFunction { + val cache: WeakCache<*, *, *> + + data class NoExtraData<Key : Any, Value : Any>( + override val cache: WeakCache<Key, Unit, Value>, + val wrapped: (Key) -> Value, + ) : CacheFunction, (Key) -> Value { + override fun invoke(p1: Key): Value { + return cache.getOrPut(p1, Unit, { a, _ -> wrapped(a) }) + } + } + + data class WithExtraData<Key : Any, ExtraKey : Any, Value : Any>( + override val cache: WeakCache<Key, ExtraKey, Value>, + val wrapped: (Key, ExtraKey) -> Value, + ) : CacheFunction, (Key, ExtraKey) -> Value { + override fun invoke(p1: Key, p2: ExtraKey): Value { + return cache.getOrPut(p1, p2, wrapped) + } + } + } } diff --git a/src/main/kotlin/util/json/DashlessUUIDSerializer.kt b/src/main/kotlin/util/json/DashlessUUIDSerializer.kt index acb1dc8..6bafebe 100644 --- a/src/main/kotlin/util/json/DashlessUUIDSerializer.kt +++ b/src/main/kotlin/util/json/DashlessUUIDSerializer.kt @@ -10,6 +10,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import moe.nea.firmament.util.parseDashlessUUID +import moe.nea.firmament.util.parsePotentiallyDashlessUUID object DashlessUUIDSerializer : KSerializer<UUID> { override val descriptor: SerialDescriptor = @@ -17,10 +18,7 @@ object DashlessUUIDSerializer : KSerializer<UUID> { override fun deserialize(decoder: Decoder): UUID { val str = decoder.decodeString() - if ("-" in str) { - return UUID.fromString(str) - } - return parseDashlessUUID(str) + return parsePotentiallyDashlessUUID(str) } override fun serialize(encoder: Encoder, value: UUID) { diff --git a/src/main/kotlin/util/math/GChainReconciliation.kt b/src/main/kotlin/util/math/GChainReconciliation.kt new file mode 100644 index 0000000..37998d5 --- /dev/null +++ b/src/main/kotlin/util/math/GChainReconciliation.kt @@ -0,0 +1,102 @@ +package moe.nea.firmament.util.math + +import kotlin.math.min + +/** + * Algorithm for (sort of) cheap reconciliation of two cycles with missing frames. + */ +object GChainReconciliation { + // Step one: Find the most common element and shift the arrays until it is at the start in both (this could be just rotating until minimal levenshtein distance or smth. that would be way better for cycles with duplicates, but i do not want to implement levenshtein as well) + // Step two: Find the first different element. + // Step three: Find the next index of both of the elements. + // Step four: Insert the element that is further away. + + fun <T> Iterable<T>.frequencies(): Map<T, Int> { + val acc = mutableMapOf<T, Int>() + for (t in this) { + acc.compute(t, { _, old -> (old ?: 0) + 1 }) + } + return acc + } + + fun <T> findMostCommonlySharedElement( + leftChain: List<T>, + rightChain: List<T>, + ): T { + val lf = leftChain.frequencies() + val rf = rightChain.frequencies() + val mostCommonlySharedElement = lf.maxByOrNull { min(it.value, rf[it.key] ?: 0) }?.key + if (mostCommonlySharedElement == null || mostCommonlySharedElement !in rf) + error("Could not find a shared element") + return mostCommonlySharedElement + } + + fun <T> List<T>.getMod(index: Int): T { + return this[index.mod(size)] + } + + fun <T> List<T>.rotated(offset: Int): List<T> { + val newList = mutableListOf<T>() + for (index in indices) { + newList.add(getMod(index - offset)) + } + return newList + } + + fun <T> shiftToFront(list: List<T>, element: T): List<T> { + val shiftDistance = list.indexOf(element) + require(shiftDistance >= 0) + return list.rotated(-shiftDistance) + } + + fun <T> List<T>.indexOfOrMaxInt(element: T): Int = indexOf(element).takeUnless { it < 0 } ?: Int.MAX_VALUE + + fun <T> reconcileCycles( + leftChain: List<T>, + rightChain: List<T>, + ): List<T> { + val mostCommonElement = findMostCommonlySharedElement(leftChain, rightChain) + val left = shiftToFront(leftChain, mostCommonElement).toMutableList() + val right = shiftToFront(rightChain, mostCommonElement).toMutableList() + + var index = 0 + while (index < left.size && index < right.size) { + val leftEl = left[index] + val rightEl = right[index] + if (leftEl == rightEl) { + index++ + continue + } + val nextLeftInRight = right.subList(index, right.size) + .indexOfOrMaxInt(leftEl) + + val nextRightInLeft = left.subList(index, left.size) + .indexOfOrMaxInt(rightEl) + if (nextLeftInRight < nextRightInLeft) { + left.add(index, rightEl) + } else if (nextRightInLeft < nextLeftInRight) { + right.add(index, leftEl) + } else { + index++ + } + } + return if (left.size < right.size) right else left + } + + fun <T> isValidCycle(longList: List<T>, cycle: List<T>): Boolean { + for ((i, value) in longList.withIndex()) { + if (cycle.getMod(i) != value) + return false + } + return true + } + + fun <T> List<T>.shortenCycle(): List<T> { + for (i in (1..<size)) { + if (isValidCycle(this, subList(0, i))) + return subList(0, i) + } + return this + } + +} diff --git a/src/main/kotlin/util/mc/ArmorUtil.kt b/src/main/kotlin/util/mc/ArmorUtil.kt new file mode 100644 index 0000000..fd1867c --- /dev/null +++ b/src/main/kotlin/util/mc/ArmorUtil.kt @@ -0,0 +1,8 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.entity.EquipmentSlot +import net.minecraft.entity.LivingEntity + +val LivingEntity.iterableArmorItems + get() = EquipmentSlot.entries.asSequence() + .map { it to getEquippedStack(it) } diff --git a/src/main/kotlin/util/mc/NbtPrism.kt b/src/main/kotlin/util/mc/NbtPrism.kt new file mode 100644 index 0000000..f034210 --- /dev/null +++ b/src/main/kotlin/util/mc/NbtPrism.kt @@ -0,0 +1,91 @@ +package moe.nea.firmament.util.mc + +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.mojang.brigadier.StringReader +import com.mojang.brigadier.arguments.ArgumentType +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.context.CommandContext +import com.mojang.brigadier.suggestion.Suggestions +import com.mojang.brigadier.suggestion.SuggestionsBuilder +import com.mojang.serialization.JsonOps +import java.util.concurrent.CompletableFuture +import kotlin.collections.indices +import kotlin.collections.map +import kotlin.jvm.optionals.getOrNull +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtList +import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.NbtString +import moe.nea.firmament.util.Base64Util + +class NbtPrism(val path: List<String>) { + companion object { + fun fromElement(path: JsonElement): NbtPrism? { + if (path is JsonArray) { + return NbtPrism(path.map { (it as JsonPrimitive).asString }) + } else if (path is JsonPrimitive && path.isString) { + return NbtPrism(path.asString.split(".")) + } + return null + } + } + + object Argument : ArgumentType<NbtPrism> { + override fun parse(reader: StringReader): NbtPrism? { + return fromElement(JsonPrimitive(StringArgumentType.string().parse(reader))) + } + + override fun getExamples(): Collection<String?>? { + return listOf("some.nbt.path", "some.other.*", "some.path.*json.in.a.json.string") + } + } + + override fun toString(): String { + return "Prism($path)" + } + + fun access(root: NbtElement): Collection<NbtElement> { + var rootSet = mutableListOf(root) + var switch = mutableListOf<NbtElement>() + for (pathSegment in path) { + if (pathSegment == ".") continue + if (pathSegment != "*" && pathSegment.startsWith("*")) { + if (pathSegment == "*json") { + for (element in rootSet) { + val eString = element.asString().getOrNull() ?: continue + val element = Gson().fromJson(eString, JsonElement::class.java) + switch.add(JsonOps.INSTANCE.convertTo(NbtOps.INSTANCE, element)) + } + } else if (pathSegment == "*base64") { + for (element in rootSet) { + val string = element.asString().getOrNull() ?: continue + switch.add(NbtString.of(Base64Util.decodeString(string))) + } + } + } + for (element in rootSet) { + if (element is NbtList) { + if (pathSegment == "*") + switch.addAll(element) + val index = pathSegment.toIntOrNull() ?: continue + if (index !in element.indices) continue + switch.add(element[index]) + } + if (element is NbtCompound) { + if (pathSegment == "*") + element.keys.mapTo(switch) { element.get(it)!! } + switch.add(element.get(pathSegment) ?: continue) + } + } + val temp = switch + switch = rootSet + rootSet = temp + switch.clear() + } + return rootSet + } +} diff --git a/src/main/kotlin/util/mc/PlayerUtil.kt b/src/main/kotlin/util/mc/PlayerUtil.kt new file mode 100644 index 0000000..53ef1f4 --- /dev/null +++ b/src/main/kotlin/util/mc/PlayerUtil.kt @@ -0,0 +1,7 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.entity.EquipmentSlot +import net.minecraft.entity.player.PlayerEntity + + +val PlayerEntity.mainHandStack get() = this.getEquippedStack(EquipmentSlot.MAINHAND) diff --git a/src/main/kotlin/util/mc/SNbtFormatter.kt b/src/main/kotlin/util/mc/SNbtFormatter.kt index e773927..e2c24f6 100644 --- a/src/main/kotlin/util/mc/SNbtFormatter.kt +++ b/src/main/kotlin/util/mc/SNbtFormatter.kt @@ -1,5 +1,6 @@ package moe.nea.firmament.util.mc +import net.minecraft.nbt.AbstractNbtList import net.minecraft.nbt.NbtByte import net.minecraft.nbt.NbtByteArray import net.minecraft.nbt.NbtCompound @@ -38,7 +39,7 @@ class SNbtFormatter private constructor() : NbtElementVisitor { override fun visitString(element: NbtString) { - result.append(NbtString.escape(element.asString())) + result.append(NbtString.escape(element.value)) } override fun visitByte(element: NbtByte) { @@ -65,18 +66,18 @@ class SNbtFormatter private constructor() : NbtElementVisitor { result.append(element.doubleValue()).append("d") } - private fun visitArrayContents(array: List<NbtElement>) { + private fun visitArrayContents(array: AbstractNbtList) { array.forEachIndexed { index, element -> writeIndent() element.accept(this) - if (array.size != index + 1) { + if (array.size() != index + 1) { result.append(",") } result.append("\n") } } - private fun writeArray(arrayTypeTag: String, array: List<NbtElement>) { + private fun writeArray(arrayTypeTag: String, array: AbstractNbtList) { result.append("[").append(arrayTypeTag).append("\n") pushIndent() visitArrayContents(array) diff --git a/src/main/kotlin/util/render/CustomRenderLayers.kt b/src/main/kotlin/util/render/CustomRenderLayers.kt new file mode 100644 index 0000000..7f3cdec --- /dev/null +++ b/src/main/kotlin/util/render/CustomRenderLayers.kt @@ -0,0 +1,74 @@ +package util.render + +import com.mojang.blaze3d.pipeline.RenderPipeline +import com.mojang.blaze3d.platform.DepthTestFunction +import com.mojang.blaze3d.vertex.VertexFormat.DrawMode +import java.util.function.Function +import net.minecraft.client.gl.RenderPipelines +import net.minecraft.client.render.RenderLayer +import net.minecraft.client.render.RenderPhase +import net.minecraft.client.render.VertexFormats +import net.minecraft.util.Identifier +import net.minecraft.util.TriState +import net.minecraft.util.Util +import moe.nea.firmament.Firmament + +object CustomRenderPipelines { + val GUI_TEXTURED_NO_DEPTH_TRIS = + RenderPipeline.builder(RenderPipelines.POSITION_TEX_COLOR_SNIPPET) + .withVertexFormat(VertexFormats.POSITION_TEXTURE_COLOR, DrawMode.TRIANGLES) + .withLocation(Firmament.identifier("gui_textured_overlay_tris")) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .withCull(false) + .withDepthWrite(false) + .build() + val OMNIPRESENT_LINES = RenderPipeline + .builder(RenderPipelines.RENDERTYPE_LINES_SNIPPET) + .withLocation(Firmament.identifier("lines")) + .withDepthWrite(false) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .build() + val COLORED_OMNIPRESENT_QUADS = + RenderPipeline.builder(RenderPipelines.MATRICES_COLOR_SNIPPET)// TODO: split this up to support better transparent ordering. + .withLocation(Firmament.identifier("colored_omnipresent_quads")) + .withVertexShader("core/position_color") + .withFragmentShader("core/position_color") + .withVertexFormat(VertexFormats.POSITION_COLOR, DrawMode.QUADS) + .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST) + .withCull(false) + .withDepthWrite(false) + .build() +} + +object CustomRenderLayers { + + + inline fun memoizeTextured(crossinline func: (Identifier) -> RenderLayer) = memoize(func) + inline fun <T, R> memoize(crossinline func: (T) -> R): Function<T, R> { + return Util.memoize { it: T -> func(it) } + } + + val GUI_TEXTURED_NO_DEPTH_TRIS = memoizeTextured { texture -> + RenderLayer.of("firmament_gui_textured_overlay_tris", + RenderLayer.DEFAULT_BUFFER_SIZE, + CustomRenderPipelines.GUI_TEXTURED_NO_DEPTH_TRIS, + RenderLayer.MultiPhaseParameters.builder().texture( + RenderPhase.Texture(texture, TriState.DEFAULT, false)) + .build(false)) + } + val LINES = RenderLayer.of( + "firmament_lines", + RenderLayer.DEFAULT_BUFFER_SIZE, + CustomRenderPipelines.OMNIPRESENT_LINES, + RenderLayer.MultiPhaseParameters.builder() // TODO: accept linewidth here + .build(false) + ) + val COLORED_QUADS = RenderLayer.of( + "firmament_quads", + RenderLayer.DEFAULT_BUFFER_SIZE, + CustomRenderPipelines.COLORED_OMNIPRESENT_QUADS, + RenderLayer.MultiPhaseParameters.builder() + .lightmap(RenderPhase.DISABLE_LIGHTMAP) + .build(false) + ) +} diff --git a/src/main/kotlin/util/render/DrawContextExt.kt b/src/main/kotlin/util/render/DrawContextExt.kt index a143d4d..fa92cd7 100644 --- a/src/main/kotlin/util/render/DrawContextExt.kt +++ b/src/main/kotlin/util/render/DrawContextExt.kt @@ -1,52 +1,24 @@ package moe.nea.firmament.util.render +import com.mojang.blaze3d.pipeline.RenderPipeline +import com.mojang.blaze3d.platform.DepthTestFunction import com.mojang.blaze3d.systems.RenderSystem +import com.mojang.blaze3d.vertex.VertexFormat.DrawMode import me.shedaniel.math.Color import org.joml.Matrix4f +import util.render.CustomRenderLayers +import net.minecraft.client.gl.RenderPipelines import net.minecraft.client.gui.DrawContext import net.minecraft.client.render.RenderLayer -import net.minecraft.client.render.RenderLayer.MultiPhaseParameters -import net.minecraft.client.render.RenderPhase -import net.minecraft.client.render.VertexFormat -import net.minecraft.client.render.VertexFormat.DrawMode import net.minecraft.client.render.VertexFormats import net.minecraft.util.Identifier -import net.minecraft.util.TriState -import net.minecraft.util.Util +import moe.nea.firmament.Firmament import moe.nea.firmament.util.MC fun DrawContext.isUntranslatedGuiDrawContext(): Boolean { return (matrices.peek().positionMatrix.properties() and Matrix4f.PROPERTY_IDENTITY.toInt()) != 0 } -object GuiRenderLayers { - val GUI_TEXTURED_NO_DEPTH = Util.memoize<Identifier, RenderLayer> { texture: Identifier -> - RenderLayer.of("firmament_gui_textured_no_depth", - VertexFormats.POSITION_TEXTURE_COLOR, - DrawMode.QUADS, - DEFAULT_BUFFER_SIZE, - MultiPhaseParameters.builder() - .texture(RenderPhase.Texture(texture, TriState.FALSE, false)) - .program(RenderPhase.POSITION_TEXTURE_COLOR_PROGRAM) - .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) - .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) - .build(false)) - } - val GUI_TEXTURED_TRIS = Util.memoize { texture: Identifier -> - RenderLayer.of("firmament_gui_textured_overlay_tris", - VertexFormats.POSITION_TEXTURE_COLOR, - DrawMode.TRIANGLES, - DEFAULT_BUFFER_SIZE, - MultiPhaseParameters.builder() - .texture(RenderPhase.Texture(texture, TriState.DEFAULT, false)) - .program(RenderPhase.POSITION_TEXTURE_COLOR_PROGRAM) - .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) - .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) - .writeMaskState(RenderPhase.COLOR_MASK) - .build(false)) - } -} - @Deprecated("Use the other drawGuiTexture") fun DrawContext.drawGuiTexture( x: Int, y: Int, z: Int, width: Int, height: Int, sprite: Identifier @@ -91,7 +63,7 @@ fun DrawContext.drawLine(fromX: Int, fromY: Int, toX: Int, toY: Int, color: Colo } RenderSystem.lineWidth(MC.window.scaleFactor.toFloat()) draw { vertexConsumers -> - val buf = vertexConsumers.getBuffer(RenderInWorldContext.RenderLayers.LINES) + val buf = vertexConsumers.getBuffer(CustomRenderLayers.LINES) buf.vertex(fromX.toFloat(), fromY.toFloat(), 0F).color(color.color) .normal(toX - fromX.toFloat(), toY - fromY.toFloat(), 0F) buf.vertex(toX.toFloat(), toY.toFloat(), 0F).color(color.color) diff --git a/src/main/kotlin/util/render/FacingThePlayerContext.kt b/src/main/kotlin/util/render/FacingThePlayerContext.kt index daa8da9..670beb6 100644 --- a/src/main/kotlin/util/render/FacingThePlayerContext.kt +++ b/src/main/kotlin/util/render/FacingThePlayerContext.kt @@ -1,18 +1,12 @@ package moe.nea.firmament.util.render -import com.mojang.blaze3d.systems.RenderSystem import io.github.notenoughupdates.moulconfig.platform.next import org.joml.Matrix4f import net.minecraft.client.font.TextRenderer -import net.minecraft.client.render.BufferRenderer -import net.minecraft.client.render.GameRenderer import net.minecraft.client.render.LightmapTextureManager import net.minecraft.client.render.RenderLayer -import net.minecraft.client.render.Tessellator import net.minecraft.client.render.VertexConsumer -import net.minecraft.client.render.VertexFormat -import net.minecraft.client.render.VertexFormats import net.minecraft.text.Text import net.minecraft.util.Identifier import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/util/render/FirmamentShaders.kt b/src/main/kotlin/util/render/FirmamentShaders.kt index ba67dbb..cc6cd49 100644 --- a/src/main/kotlin/util/render/FirmamentShaders.kt +++ b/src/main/kotlin/util/render/FirmamentShaders.kt @@ -1,9 +1,10 @@ package moe.nea.firmament.util.render +import com.mojang.blaze3d.vertex.VertexFormat +import net.minecraft.client.gl.CompiledShader import net.minecraft.client.gl.Defines -import net.minecraft.client.gl.ShaderProgramKey +import net.minecraft.client.gl.ShaderProgram import net.minecraft.client.render.RenderPhase -import net.minecraft.client.render.VertexFormat import net.minecraft.client.render.VertexFormats import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe @@ -11,20 +12,9 @@ import moe.nea.firmament.events.DebugInstantiateEvent import moe.nea.firmament.util.MC object FirmamentShaders { - val shaders = mutableListOf<ShaderProgramKey>() - - private fun shader(name: String, format: VertexFormat, defines: Defines): ShaderProgramKey { - val key = ShaderProgramKey(Firmament.identifier(name), format, defines) - shaders.add(key) - return key - } - - val LINES = RenderPhase.ShaderProgram(shader("core/rendertype_lines", VertexFormats.LINES, Defines.EMPTY)) @Subscribe fun debugLoad(event: DebugInstantiateEvent) { - shaders.forEach { - MC.instance.shaderLoader.getOrCreateProgram(it) - } + // TODO: do i still need to work with shaders like this? } } diff --git a/src/main/kotlin/util/render/RenderCircleProgress.kt b/src/main/kotlin/util/render/RenderCircleProgress.kt index 805633c..d759033 100644 --- a/src/main/kotlin/util/render/RenderCircleProgress.kt +++ b/src/main/kotlin/util/render/RenderCircleProgress.kt @@ -1,18 +1,12 @@ package moe.nea.firmament.util.render -import com.mojang.blaze3d.systems.RenderSystem import io.github.notenoughupdates.moulconfig.platform.next import org.joml.Matrix4f import org.joml.Vector2f +import util.render.CustomRenderLayers import kotlin.math.atan2 import kotlin.math.tan import net.minecraft.client.gui.DrawContext -import net.minecraft.client.render.BufferRenderer -import net.minecraft.client.render.RenderLayer -import net.minecraft.client.render.RenderPhase -import net.minecraft.client.render.Tessellator -import net.minecraft.client.render.VertexFormat.DrawMode -import net.minecraft.client.render.VertexFormats import net.minecraft.util.Identifier object RenderCircleProgress { @@ -26,9 +20,8 @@ object RenderCircleProgress { v1: Float, v2: Float, ) { - RenderSystem.enableBlend() drawContext.draw { - val bufferBuilder = it.getBuffer(GuiRenderLayers.GUI_TEXTURED_TRIS.apply(texture)) + val bufferBuilder = it.getBuffer(CustomRenderLayers.GUI_TEXTURED_NO_DEPTH_TRIS.apply(texture)) val matrix: Matrix4f = drawContext.matrices.peek().positionMatrix val corners = listOf( @@ -86,7 +79,6 @@ object RenderCircleProgress { .next() } } - RenderSystem.disableBlend() } diff --git a/src/main/kotlin/util/render/RenderInWorldContext.kt b/src/main/kotlin/util/render/RenderInWorldContext.kt index bb58200..98b10ca 100644 --- a/src/main/kotlin/util/render/RenderInWorldContext.kt +++ b/src/main/kotlin/util/render/RenderInWorldContext.kt @@ -5,15 +5,12 @@ import io.github.notenoughupdates.moulconfig.platform.next import java.lang.Math.pow import org.joml.Matrix4f import org.joml.Vector3f -import net.minecraft.client.gl.VertexBuffer +import util.render.CustomRenderLayers import net.minecraft.client.render.Camera import net.minecraft.client.render.RenderLayer -import net.minecraft.client.render.RenderPhase import net.minecraft.client.render.RenderTickCounter -import net.minecraft.client.render.Tessellator import net.minecraft.client.render.VertexConsumer import net.minecraft.client.render.VertexConsumerProvider -import net.minecraft.client.render.VertexFormat import net.minecraft.client.render.VertexFormats import net.minecraft.client.texture.Sprite import net.minecraft.client.util.math.MatrixStack @@ -27,47 +24,12 @@ import moe.nea.firmament.util.MC @RenderContextDSL class RenderInWorldContext private constructor( - private val tesselator: Tessellator, val matrixStack: MatrixStack, private val camera: Camera, private val tickCounter: RenderTickCounter, val vertexConsumers: VertexConsumerProvider.Immediate, ) { - object RenderLayers { - val TRANSLUCENT_TRIS = RenderLayer.of("firmament_translucent_tris", - VertexFormats.POSITION_COLOR, - VertexFormat.DrawMode.TRIANGLES, - RenderLayer.CUTOUT_BUFFER_SIZE, - false, true, - RenderLayer.MultiPhaseParameters.builder() - .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) - .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) - .program(RenderPhase.POSITION_COLOR_PROGRAM) - .build(false)) - val LINES = RenderLayer.of("firmament_rendertype_lines", - VertexFormats.LINES, - VertexFormat.DrawMode.LINES, - RenderLayer.CUTOUT_BUFFER_SIZE, - false, false, // do we need translucent? i dont think so - RenderLayer.MultiPhaseParameters.builder() - .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) - .program(FirmamentShaders.LINES) - .build(false) - ) - val COLORED_QUADS = RenderLayer.of( - "firmament_quads", - VertexFormats.POSITION_COLOR, - VertexFormat.DrawMode.QUADS, - RenderLayer.CUTOUT_BUFFER_SIZE, - false, true, - RenderLayer.MultiPhaseParameters.builder() - .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) - .program(RenderPhase.POSITION_COLOR_PROGRAM) - .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) - .build(false) - ) - } @Deprecated("stateful color management is no longer a thing") fun color(color: me.shedaniel.math.Color) { @@ -82,7 +44,7 @@ class RenderInWorldContext private constructor( fun block(blockPos: BlockPos, color: Int) { matrixStack.push() matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) - buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(RenderLayers.COLORED_QUADS), color) + buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(CustomRenderLayers.COLORED_QUADS), color) matrixStack.pop() } @@ -155,7 +117,7 @@ class RenderInWorldContext private constructor( matrixStack.translate(vec3d.x, vec3d.y, vec3d.z) matrixStack.scale(size, size, size) matrixStack.translate(-.5, -.5, -.5) - buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(RenderLayers.COLORED_QUADS), color) + buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(CustomRenderLayers.COLORED_QUADS), color) matrixStack.pop() vertexConsumers.draw() } @@ -182,8 +144,7 @@ class RenderInWorldContext private constructor( fun line(points: List<Vec3d>, lineWidth: Float = 10F) { RenderSystem.lineWidth(lineWidth) - // TODO: replace with renderlayers - val buffer = tesselator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES) + val buffer = vertexConsumers.getBuffer(CustomRenderLayers.LINES) val matrix = matrixStack.peek() var lastNormal: Vector3f? = null @@ -203,7 +164,6 @@ class RenderInWorldContext private constructor( .next() } - RenderLayers.LINES.draw(buffer.end()) } // TODO: put the favourite icons in front of items again @@ -281,16 +241,15 @@ class RenderInWorldContext private constructor( fun renderInWorld(event: WorldRenderLastEvent, block: RenderInWorldContext. () -> Unit) { // TODO: there should be *no more global state*. the only thing we should be doing is render layers. that includes settings like culling, blending, shader color, and depth testing // For now i will let these functions remain, but this needs to go before i do a full (non-beta) release - RenderSystem.disableDepthTest() - RenderSystem.enableBlend() - RenderSystem.defaultBlendFunc() - RenderSystem.disableCull() +// RenderSystem.disableDepthTest() +// RenderSystem.enableBlend() +// RenderSystem.defaultBlendFunc() +// RenderSystem.disableCull() event.matrices.push() event.matrices.translate(-event.camera.pos.x, -event.camera.pos.y, -event.camera.pos.z) val ctx = RenderInWorldContext( - RenderSystem.renderThreadTesselator(), event.matrices, event.camera, event.tickCounter, @@ -302,10 +261,6 @@ class RenderInWorldContext private constructor( event.matrices.pop() event.vertexConsumers.draw() RenderSystem.setShaderColor(1F, 1F, 1F, 1F) - VertexBuffer.unbind() - RenderSystem.enableDepthTest() - RenderSystem.enableCull() - RenderSystem.disableBlend() } } } diff --git a/src/main/kotlin/util/render/TintedOverlayTexture.kt b/src/main/kotlin/util/render/TintedOverlayTexture.kt index a02eccc..0677846 100644 --- a/src/main/kotlin/util/render/TintedOverlayTexture.kt +++ b/src/main/kotlin/util/render/TintedOverlayTexture.kt @@ -1,7 +1,5 @@ package moe.nea.firmament.util.render -import com.mojang.blaze3d.platform.GlConst -import com.mojang.blaze3d.systems.RenderSystem import me.shedaniel.math.Color import net.minecraft.client.render.OverlayTexture import net.minecraft.util.math.ColorHelper @@ -29,16 +27,9 @@ class TintedOverlayTexture : OverlayTexture() { } } - RenderSystem.activeTexture(GlConst.GL_TEXTURE1) - texture.bindTexture() texture.setFilter(false, false) texture.setClamp(true) - image.upload(0, - 0, 0, - 0, 0, - image.width, image.height, - false) - RenderSystem.activeTexture(GlConst.GL_TEXTURE0) + texture.upload() return this } } diff --git a/src/main/kotlin/util/skyblock/SackUtil.kt b/src/main/kotlin/util/skyblock/SackUtil.kt index fd67c44..c46542e 100644 --- a/src/main/kotlin/util/skyblock/SackUtil.kt +++ b/src/main/kotlin/util/skyblock/SackUtil.kt @@ -93,7 +93,7 @@ object SackUtil { fun updateFromHoverText(text: Text) { text.siblings.forEach(::updateFromHoverText) - val hoverText = text.style.hoverEvent?.getValue(HoverEvent.Action.SHOW_TEXT) ?: return + val hoverText = (text.style.hoverEvent as? HoverEvent.ShowText)?.value ?: return val cleanedText = hoverText.unformattedString if (cleanedText.startsWith("Added items:\n")) { if (!foundAdded) { diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt index 806f61e..2458891 100644 --- a/src/main/kotlin/util/textutil.kt +++ b/src/main/kotlin/util/textutil.kt @@ -127,13 +127,13 @@ fun MutableText.darkGrey() = withColor(Formatting.DARK_GRAY) fun MutableText.red() = withColor(Formatting.RED) fun MutableText.white() = withColor(Formatting.WHITE) fun MutableText.bold(): MutableText = styled { it.withBold(true) } -fun MutableText.hover(text: Text): MutableText = styled {it.withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, text))} +fun MutableText.hover(text: Text): MutableText = styled {it.withHoverEvent(HoverEvent.ShowText(text))} fun MutableText.clickCommand(command: String): MutableText { require(command.startsWith("/")) return this.styled { - it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, command)) + it.withClickEvent(ClickEvent.RunCommand(command)) } } @@ -164,4 +164,14 @@ fun Text.transformEachRecursively(function: (Text) -> Text): Text { fun tr(key: String, default: String): MutableText = error("Compiler plugin did not run.") fun trResolved(key: String, vararg args: Any): MutableText = Text.stringifiedTranslatable(key, *args) +fun titleCase(str: String): String { + return str + .lowercase() + .replace("_", " ") + .split(" ") + .joinToString(" ") { word -> + word.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } + } +} + diff --git a/src/main/kotlin/util/uuid.kt b/src/main/kotlin/util/uuid.kt index cccfdd2..14aa83d 100644 --- a/src/main/kotlin/util/uuid.kt +++ b/src/main/kotlin/util/uuid.kt @@ -3,6 +3,12 @@ package moe.nea.firmament.util import java.math.BigInteger import java.util.UUID +fun parsePotentiallyDashlessUUID(unknownFormattedUUID: String): UUID { + if ("-" in unknownFormattedUUID) + return UUID.fromString(unknownFormattedUUID) + return parseDashlessUUID(unknownFormattedUUID) +} + fun parseDashlessUUID(dashlessUuid: String): UUID { val most = BigInteger(dashlessUuid.substring(0, 16), 16) val least = BigInteger(dashlessUuid.substring(16, 32), 16) diff --git a/src/main/resources/assets/firmament/gui/license_viewer/index.xml b/src/main/resources/assets/firmament/gui/license_viewer/index.xml new file mode 100644 index 0000000..c23153d --- /dev/null +++ b/src/main/resources/assets/firmament/gui/license_viewer/index.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<Root xmlns="http://notenoughupdates.org/moulconfig" + xmlns:firm="http://firmament.nea.moe/moulconfig" +> + <Center> + <Panel background="VANILLA"> + <Column> + <Center> + <Scale scale="2"> + <Text text="Firmament Licenses"/> + </Scale> + </Center> + <!-- <firm:Line/>--> + <ScrollPanel width="306" height="250"> + <Panel insets="3" background="TRANSPARENT"> + <Array data="@softwares"> + <Center> + <firm:Fixed width="300"> + <Panel background="VANILLA" insets="8"> + <Column> + <Scale scale="1.2"> + <Text text="@projectName"/> + </Scale> + <When condition="@hasWebPresence"> + <Row> + <firm:Button onClick="@open"> + <Text text="Navigate to WebSite"/> + </firm:Button> + </Row> + <Spacer/> + </When> + <Text text="@projectDescription" width="280"/> + <Array data="@developers"> + <Row> + <Text text="by "/> + <Text text="@name"/> + </Row> + </Array> + <Array data="@licenses"> + <When condition="@hasUrl"> + <firm:Button onClick="@open"> + <Center> + <Row> + <Text text="License: "/> + <Text text="@name"/> + </Row> + </Center> + </firm:Button> + <Row> + <Text text="License: "/> + <Text text="@name"/> + </Row> + </When> + </Array> + </Column> + </Panel> + </firm:Fixed> + </Center> + </Array> + </Panel> + </ScrollPanel> + </Column> + </Panel> + </Center> +</Root> diff --git a/src/main/resources/assets/firmament/logo.png b/src/main/resources/assets/firmament/logo.png Binary files differindex e00a2fa..e3f063a 100644 --- a/src/main/resources/assets/firmament/logo.png +++ b/src/main/resources/assets/firmament/logo.png diff --git a/src/main/resources/firmament.accesswidener b/src/main/resources/firmament.accesswidener index fd79cb5..eb78b8b 100644 --- a/src/main/resources/firmament.accesswidener +++ b/src/main/resources/firmament.accesswidener @@ -6,12 +6,11 @@ accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARA accessible field net/minecraft/client/network/ClientPlayNetworkHandler combinedDynamicRegistries Lnet/minecraft/registry/DynamicRegistryManager$Immutable; accessible method net/minecraft/registry/RegistryOps <init> (Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/registry/RegistryOps$RegistryInfoGetter;)V accessible class net/minecraft/registry/RegistryOps$CachedRegistryInfoGetter +accessible class net/minecraft/client/render/model/ModelBaker$BakerImpl +accessible method net/minecraft/client/render/model/ModelBaker$BakerImpl <init> (Lnet/minecraft/client/render/model/ModelBaker;Lnet/minecraft/client/render/model/ErrorCollectingSpriteGetter;)V accessible field net/minecraft/entity/mob/CreeperEntity CHARGED Lnet/minecraft/entity/data/TrackedData; accessible method net/minecraft/entity/decoration/ArmorStandEntity setSmall (Z)V -accessible field net/minecraft/entity/passive/AbstractHorseEntity items Lnet/minecraft/inventory/SimpleInventory; -accessible field net/minecraft/entity/passive/AbstractHorseEntity SADDLED_FLAG I -accessible field net/minecraft/entity/passive/AbstractHorseEntity HORSE_FLAGS Lnet/minecraft/entity/data/TrackedData; accessible method net/minecraft/resource/NamespaceResourceManager loadMetadata (Lnet/minecraft/resource/InputSupplier;)Lnet/minecraft/resource/metadata/ResourceMetadata; accessible method net/minecraft/client/gui/DrawContext drawTexturedQuad (Ljava/util/function/Function;Lnet/minecraft/util/Identifier;IIIIFFFFI)V @@ -26,3 +25,5 @@ accessible method net/minecraft/client/render/RenderPhase$Texture getId ()Ljava/ accessible field net/minecraft/client/render/RenderLayer$MultiPhase phases Lnet/minecraft/client/render/RenderLayer$MultiPhaseParameters; accessible field net/minecraft/client/render/RenderLayer$MultiPhaseParameters texture Lnet/minecraft/client/render/RenderPhase$TextureBase; accessible field net/minecraft/client/network/ClientPlayerInteractionManager currentBreakingPos Lnet/minecraft/util/math/BlockPos; + +mutable field net/minecraft/client/render/entity/state/LivingEntityRenderState headItemRenderState Lnet/minecraft/client/render/item/ItemRenderState; diff --git a/src/test/kotlin/MixinTest.kt b/src/test/kotlin/MixinTest.kt new file mode 100644 index 0000000..55aa7c2 --- /dev/null +++ b/src/test/kotlin/MixinTest.kt @@ -0,0 +1,34 @@ +package moe.nea.firmament.test + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.spongepowered.asm.mixin.MixinEnvironment +import org.spongepowered.asm.mixin.transformer.IMixinTransformer +import moe.nea.firmament.init.MixinPlugin + +class MixinTest { + @Test + fun mixinAudit() { + FirmTestBootstrap.bootstrapMinecraft() + MixinEnvironment.getCurrentEnvironment().audit() + val mp = MixinPlugin.instances.single() + Assertions.assertEquals( + mp.expectedFullPathMixins, + mp.appliedFullPathMixins, + ) + Assertions.assertNotEquals( + 0, + mp.mixins.size + ) + + } + + @Test + fun hasInstalledMixinTransformer() { + Assertions.assertInstanceOf( + IMixinTransformer::class.java, + MixinEnvironment.getCurrentEnvironment().activeTransformer + ) + } +} + diff --git a/src/test/kotlin/root.kt b/src/test/kotlin/root.kt index 045fdd5..000ddda 100644 --- a/src/test/kotlin/root.kt +++ b/src/test/kotlin/root.kt @@ -24,6 +24,7 @@ object FirmTestBootstrap { println("Bootstrap completed at $loadEnd after $loadDuration") } + @JvmStatic fun bootstrapMinecraft() { } } diff --git a/src/test/kotlin/testutil/AutoBootstrapExtension.kt b/src/test/kotlin/testutil/AutoBootstrapExtension.kt new file mode 100644 index 0000000..6f225a0 --- /dev/null +++ b/src/test/kotlin/testutil/AutoBootstrapExtension.kt @@ -0,0 +1,14 @@ +package moe.nea.firmament.test.testutil + +import com.google.auto.service.AutoService +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.Extension +import org.junit.jupiter.api.extension.ExtensionContext +import moe.nea.firmament.test.FirmTestBootstrap + +@AutoService(Extension::class) +class AutoBootstrapExtension : Extension, BeforeAllCallback { + override fun beforeAll(p0: ExtensionContext) { + FirmTestBootstrap.bootstrapMinecraft() + } +} diff --git a/src/test/kotlin/testutil/ItemResources.kt b/src/test/kotlin/testutil/ItemResources.kt index 107b565..17198f1 100644 --- a/src/test/kotlin/testutil/ItemResources.kt +++ b/src/test/kotlin/testutil/ItemResources.kt @@ -1,13 +1,23 @@ package moe.nea.firmament.test.testutil +import com.mojang.datafixers.DSL +import com.mojang.datafixers.DataFixUtils +import com.mojang.datafixers.types.templates.Named +import com.mojang.serialization.Dynamic +import com.mojang.serialization.JsonOps +import net.minecraft.SharedConstants +import net.minecraft.datafixer.Schemas +import net.minecraft.datafixer.TypeReferences import net.minecraft.item.ItemStack import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.NbtString import net.minecraft.nbt.StringNbtReader import net.minecraft.registry.RegistryOps import net.minecraft.text.Text import net.minecraft.text.TextCodecs +import moe.nea.firmament.features.debug.ExportedTestConstantMeta import moe.nea.firmament.test.FirmTestBootstrap import moe.nea.firmament.util.MC @@ -24,18 +34,49 @@ object ItemResources { } fun loadSNbt(path: String): NbtCompound { - return StringNbtReader.parse(loadString(path)) + return StringNbtReader.readCompound(loadString(path)) } fun getNbtOps(): RegistryOps<NbtElement> = MC.currentOrDefaultRegistries.getOps(NbtOps.INSTANCE) + fun tryMigrateNbt( + nbtCompound: NbtCompound, + typ: DSL.TypeReference, + ): NbtElement { + val source = nbtCompound.get("source", ExportedTestConstantMeta.CODEC) + nbtCompound.remove("source") + if (source.isPresent) { + val wrappedNbtSource = if (typ == TypeReferences.TEXT_COMPONENT && source.get().dataVersion < 4325) { + // Per 1.21.5 text components are wrapped in a string, which firmament unwrapped in the snbt files + NbtString.of( + NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, nbtCompound) + .toString()) + } else { + nbtCompound + } + return Schemas.getFixer() + .update( + typ, + Dynamic(NbtOps.INSTANCE, wrappedNbtSource), + source.get().dataVersion, + SharedConstants.getGameVersion().saveVersion.id + ).value + } + return nbtCompound + } + fun loadText(name: String): Text { - return TextCodecs.CODEC.parse(getNbtOps(), loadSNbt("testdata/chat/$name.snbt")) - .getOrThrow { IllegalStateException("Could not load test chat '$name': $it") } + return TextCodecs.CODEC.parse( + getNbtOps(), + tryMigrateNbt(loadSNbt("testdata/chat/$name.snbt"), TypeReferences.TEXT_COMPONENT) + ).getOrThrow { IllegalStateException("Could not load test chat '$name': $it") } } fun loadItem(name: String): ItemStack { - // TODO: make the load work with enchantments - return ItemStack.CODEC.parse(getNbtOps(), loadSNbt("testdata/items/$name.snbt")) - .getOrThrow { IllegalStateException("Could not load test item '$name': $it") } + try { + val itemNbt = loadSNbt("testdata/items/$name.snbt") + return ItemStack.CODEC.parse(getNbtOps(), tryMigrateNbt(itemNbt, TypeReferences.ITEM_STACK)).orThrow + } catch (ex: Exception) { + throw RuntimeException("Could not load item resource '$name'", ex) + } } } diff --git a/src/test/kotlin/testutil/KotestPlugin.kt b/src/test/kotlin/testutil/KotestPlugin.kt deleted file mode 100644 index 6db50fb..0000000 --- a/src/test/kotlin/testutil/KotestPlugin.kt +++ /dev/null @@ -1,16 +0,0 @@ -package moe.nea.firmament.test.testutil - -import io.kotest.core.config.AbstractProjectConfig -import io.kotest.core.extensions.Extension -import moe.nea.firmament.test.FirmTestBootstrap - -class KotestPlugin : AbstractProjectConfig() { - override fun extensions(): List<Extension> { - return listOf() - } - - override suspend fun beforeProject() { - FirmTestBootstrap.bootstrapMinecraft() - super.beforeProject() - } -} diff --git a/src/test/kotlin/util/ColorCodeTest.kt b/src/test/kotlin/util/ColorCodeTest.kt index 949749e..7c581c5 100644 --- a/src/test/kotlin/util/ColorCodeTest.kt +++ b/src/test/kotlin/util/ColorCodeTest.kt @@ -1,57 +1,57 @@ package moe.nea.firmament.test.util -import io.kotest.core.spec.style.AnnotationSpec import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test import moe.nea.firmament.util.removeColorCodes -class ColorCodeTest : AnnotationSpec() { - @Test - fun testWhatever() { - Assertions.assertEquals("", "".removeColorCodes()) - Assertions.assertEquals("", "§".removeColorCodes()) - Assertions.assertEquals("", "§a".removeColorCodes()) - Assertions.assertEquals("ab", "a§ab".removeColorCodes()) - Assertions.assertEquals("ab", "a§ab§§".removeColorCodes()) - Assertions.assertEquals("abc", "a§ab§§c".removeColorCodes()) - Assertions.assertEquals("bc", "§ab§§c".removeColorCodes()) - Assertions.assertEquals("b§lc", "§ab§l§§c".removeColorCodes(true)) - Assertions.assertEquals("b§lc§l", "§ab§l§§c§l".removeColorCodes(true)) - Assertions.assertEquals("§lb§lc", "§l§ab§l§§c".removeColorCodes(true)) - } - - @Test - fun testEdging() { - Assertions.assertEquals("", "§".removeColorCodes()) - Assertions.assertEquals("a", "a§".removeColorCodes()) - Assertions.assertEquals("b", "§ab§".removeColorCodes()) - } - - @Test - fun `testDouble§`() { - Assertions.assertEquals("1", "§§1".removeColorCodes()) - } - - @Test - fun testKeepNonColor() { - Assertions.assertEquals("§k§l§m§n§o§r", "§k§l§m§f§n§o§r".removeColorCodes(true)) - } - - @Test - fun testPlainString() { - Assertions.assertEquals("bcdefgp", "bcdefgp".removeColorCodes()) - Assertions.assertEquals("", "".removeColorCodes()) - } - - @Test - fun testSomeNormalTestCases() { - Assertions.assertEquals( - "You are not currently in a party.", - "§r§cYou are not currently in a party.§r".removeColorCodes() - ) - Assertions.assertEquals( - "Ancient Necron's Chestplate ✪✪✪✪", - "§dAncient Necron's Chestplate §6✪§6✪§6✪§6✪".removeColorCodes() - ) - } +class ColorCodeTest { + @Test + fun testWhatever() { + Assertions.assertEquals("", "".removeColorCodes()) + Assertions.assertEquals("", "§".removeColorCodes()) + Assertions.assertEquals("", "§a".removeColorCodes()) + Assertions.assertEquals("ab", "a§ab".removeColorCodes()) + Assertions.assertEquals("ab", "a§ab§§".removeColorCodes()) + Assertions.assertEquals("abc", "a§ab§§c".removeColorCodes()) + Assertions.assertEquals("bc", "§ab§§c".removeColorCodes()) + Assertions.assertEquals("b§lc", "§ab§l§§c".removeColorCodes(true)) + Assertions.assertEquals("b§lc§l", "§ab§l§§c§l".removeColorCodes(true)) + Assertions.assertEquals("§lb§lc", "§l§ab§l§§c".removeColorCodes(true)) + } + + @Test + fun testEdging() { + Assertions.assertEquals("", "§".removeColorCodes()) + Assertions.assertEquals("a", "a§".removeColorCodes()) + Assertions.assertEquals("b", "§ab§".removeColorCodes()) + } + + @Test + fun `testDouble§`() { + Assertions.assertEquals("1", "§§1".removeColorCodes()) + } + + @Test + fun testKeepNonColor() { + Assertions.assertEquals("§k§l§m§n§o§r", "§k§l§m§f§n§o§r".removeColorCodes(true)) + } + + @Test + fun testPlainString() { + Assertions.assertEquals("bcdefgp", "bcdefgp".removeColorCodes()) + Assertions.assertEquals("", "".removeColorCodes()) + } + + @Test + fun testSomeNormalTestCases() { + Assertions.assertEquals( + "You are not currently in a party.", + "§r§cYou are not currently in a party.§r".removeColorCodes() + ) + Assertions.assertEquals( + "Ancient Necron's Chestplate ✪✪✪✪", + "§dAncient Necron's Chestplate §6✪§6✪§6✪§6✪".removeColorCodes() + ) + } } diff --git a/src/test/kotlin/util/TextUtilText.kt b/src/test/kotlin/util/TextUtilText.kt index 46ed3b4..94ab222 100644 --- a/src/test/kotlin/util/TextUtilText.kt +++ b/src/test/kotlin/util/TextUtilText.kt @@ -1,16 +1,18 @@ package moe.nea.firmament.test.util -import io.kotest.core.spec.style.AnnotationSpec import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test import moe.nea.firmament.test.testutil.ItemResources import moe.nea.firmament.util.getLegacyFormatString -class TextUtilText : AnnotationSpec() { +class TextUtilText { @Test fun testThing() { // TODO: add more tests that are directly validated with 1.8.9 code val text = ItemResources.loadText("all-chat") - Assertions.assertEquals("§r§r§8[§r§9302§r§8] §r§6♫ §r§b[MVP§r§d+§r§b] lrg89§r§f: test§r", - text.getLegacyFormatString()) + Assertions.assertEquals( + "§r§r§8[§r§9302§r§8] §r§6♫ §r§b[MVP§r§d+§r§b] lrg89§r§f: test§r", + text.getLegacyFormatString() + ) } } diff --git a/src/test/kotlin/util/math/GChainReconciliationTest.kt b/src/test/kotlin/util/math/GChainReconciliationTest.kt new file mode 100644 index 0000000..380ea5c --- /dev/null +++ b/src/test/kotlin/util/math/GChainReconciliationTest.kt @@ -0,0 +1,75 @@ +package moe.nea.firmament.test.util.math + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import moe.nea.firmament.util.math.GChainReconciliation +import moe.nea.firmament.util.math.GChainReconciliation.rotated + +class GChainReconciliationTest { + + fun <T> assertEqualCycles( + expected: List<T>, + actual: List<T> + ) { + for (offset in expected.indices) { + val rotated = expected.rotated(offset) + val matchesAtRotation = run { + for ((i, v) in actual.withIndex()) { + if (rotated[i % rotated.size] != v) + return@run false + } + true + } + if (matchesAtRotation) + return + } + assertEquals(expected, actual, "Expected arrays to be cycle equivalent") + } + + @Test + fun testUnfixableCycleNotBeingModified() { + assertEquals( + listOf(1, 2, 3, 4, 6, 1, 2, 3, 4, 6), + GChainReconciliation.reconcileCycles( + listOf(1, 2, 3, 4, 6, 1, 2, 3, 4, 6), + listOf(2, 3, 4, 5, 1, 2, 3, 4, 5, 1) + ) + ) + } + + @Test + fun testMultipleIndependentHoles() { + assertEqualCycles( + listOf(1, 2, 3, 4, 5, 6), + GChainReconciliation.reconcileCycles( + listOf(1, 3, 4, 5, 6, 1, 3, 4, 5, 6), + listOf(2, 3, 4, 5, 1, 2, 3, 4, 5, 1) + ) + ) + + } + + @Test + fun testBigHole() { + assertEqualCycles( + listOf(1, 2, 3, 4, 5, 6), + GChainReconciliation.reconcileCycles( + listOf(1, 4, 5, 6, 1, 4, 5, 6), + listOf(2, 3, 4, 5, 1, 2, 3, 4, 5, 1) + ) + ) + + } + + @Test + fun testOneMissingBeingDetected() { + assertEqualCycles( + listOf(1, 2, 3, 4, 5, 6), + GChainReconciliation.reconcileCycles( + listOf(1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6), + listOf(2, 3, 4, 5, 1, 2, 3, 4, 5, 1) + ) + ) + } +} diff --git a/src/test/kotlin/util/skyblock/AbilityUtilsTest.kt b/src/test/kotlin/util/skyblock/AbilityUtilsTest.kt index 206a357..9d25aad 100644 --- a/src/test/kotlin/util/skyblock/AbilityUtilsTest.kt +++ b/src/test/kotlin/util/skyblock/AbilityUtilsTest.kt @@ -1,7 +1,7 @@ package moe.nea.firmament.test.util.skyblock -import io.kotest.core.spec.style.AnnotationSpec import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds import net.minecraft.text.Text @@ -9,7 +9,7 @@ import moe.nea.firmament.test.testutil.ItemResources import moe.nea.firmament.util.skyblock.AbilityUtils import moe.nea.firmament.util.unformattedString -class AbilityUtilsTest : AnnotationSpec() { +class AbilityUtilsTest { fun List<AbilityUtils.ItemAbility>.stripDescriptions() = map { it.copy(descriptionLines = it.descriptionLines.map { Text.literal(it.unformattedString) }) @@ -24,9 +24,11 @@ class AbilityUtilsTest : AnnotationSpec() { false, AbilityUtils.AbilityActivation.RIGHT_CLICK, null, - listOf("Throw your pickaxe to create an", - "explosion mining all ores in a 3 block", - "radius.").map(Text::literal), + listOf( + "Throw your pickaxe to create an", + "explosion mining all ores in a 3 block", + "radius." + ).map(Text::literal), 48.seconds ) ), @@ -43,8 +45,10 @@ class AbilityUtilsTest : AnnotationSpec() { true, AbilityUtils.AbilityActivation.RIGHT_CLICK, null, - listOf("Grants +200% ⸕ Mining Speed for", - "10s.").map(Text::literal), + listOf( + "Grants +200% ⸕ Mining Speed for", + "10s." + ).map(Text::literal), 2.minutes ) ), @@ -58,8 +62,10 @@ class AbilityUtilsTest : AnnotationSpec() { listOf( AbilityUtils.ItemAbility( "Instant Transmission", true, AbilityUtils.AbilityActivation.RIGHT_CLICK, 23, - listOf("Teleport 12 blocks ahead of you and", - "gain +50 ✦ Speed for 3 seconds.").map(Text::literal), + listOf( + "Teleport 12 blocks ahead of you and", + "gain +50 ✦ Speed for 3 seconds." + ).map(Text::literal), null ), AbilityUtils.ItemAbility( @@ -67,9 +73,11 @@ class AbilityUtilsTest : AnnotationSpec() { false, AbilityUtils.AbilityActivation.SNEAK_RIGHT_CLICK, 90, - listOf("Teleport to your targeted block up", - "to 61 blocks away.", - "Soulflow Cost: 1").map(Text::literal), + listOf( + "Teleport to your targeted block up", + "to 61 blocks away.", + "Soulflow Cost: 1" + ).map(Text::literal), null ) ), diff --git a/src/test/kotlin/util/skyblock/ItemTypeTest.kt b/src/test/kotlin/util/skyblock/ItemTypeTest.kt index cca3d13..c0ef2a3 100644 --- a/src/test/kotlin/util/skyblock/ItemTypeTest.kt +++ b/src/test/kotlin/util/skyblock/ItemTypeTest.kt @@ -1,26 +1,28 @@ package moe.nea.firmament.test.util.skyblock -import io.kotest.core.spec.style.ShouldSpec -import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.TestFactory import moe.nea.firmament.test.testutil.ItemResources import moe.nea.firmament.util.skyblock.ItemType -class ItemTypeTest - : ShouldSpec( - { - context("ItemType.fromItemstack") { - listOf( - "pets/lion-item" to ItemType.PET, - "pets/rabbit-selected" to ItemType.PET, - "pets/mithril-golem-not-selected" to ItemType.PET, - "aspect-of-the-void" to ItemType.SWORD, - "titanium-drill" to ItemType.DRILL, - "diamond-pickaxe" to ItemType.PICKAXE, - "gemstone-gauntlet" to ItemType.GAUNTLET, - ).forEach { (name, typ) -> - should("return $typ for $name") { - ItemType.fromItemStack(ItemResources.loadItem(name)) shouldBe typ - } +class ItemTypeTest { + @TestFactory + fun fromItemstack() = + listOf( + "pets/lion-item" to ItemType.PET, + "pets/rabbit-selected" to ItemType.PET, + "pets/mithril-golem-not-selected" to ItemType.PET, + "aspect-of-the-void" to ItemType.SWORD, + "titanium-drill" to ItemType.DRILL, + "diamond-pickaxe" to ItemType.PICKAXE, + "gemstone-gauntlet" to ItemType.GAUNTLET, + ).map { (name, typ) -> + DynamicTest.dynamicTest("return $typ for $name") { + Assertions.assertEquals( + typ, + ItemType.fromItemStack(ItemResources.loadItem(name)) + ) } } - }) +} diff --git a/src/test/kotlin/util/skyblock/SackUtilTest.kt b/src/test/kotlin/util/skyblock/SackUtilTest.kt index f93cd2b..e0e3e63 100644 --- a/src/test/kotlin/util/skyblock/SackUtilTest.kt +++ b/src/test/kotlin/util/skyblock/SackUtilTest.kt @@ -1,12 +1,12 @@ package moe.nea.firmament.test.util.skyblock -import io.kotest.core.spec.style.AnnotationSpec import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test import moe.nea.firmament.test.testutil.ItemResources import moe.nea.firmament.util.skyblock.SackUtil import moe.nea.firmament.util.skyblock.SkyBlockItems -class SackUtilTest : AnnotationSpec() { +class SackUtilTest { @Test fun testOneRottenFlesh() { Assertions.assertEquals( diff --git a/src/test/resources/testdata/chat/all-chat.snbt b/src/test/resources/testdata/chat/all-chat.snbt index 15cc2de..386194b 100644 --- a/src/test/resources/testdata/chat/all-chat.snbt +++ b/src/test/resources/testdata/chat/all-chat.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, extra: [ { bold: 0b, diff --git a/src/test/resources/testdata/chat/sacks/gain-and-lose-regular.snbt b/src/test/resources/testdata/chat/sacks/gain-and-lose-regular.snbt index 924a558..d7b8b90 100644 --- a/src/test/resources/testdata/chat/sacks/gain-and-lose-regular.snbt +++ b/src/test/resources/testdata/chat/sacks/gain-and-lose-regular.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, color: "#FFAA00", extra: [ { diff --git a/src/test/resources/testdata/chat/sacks/gain-rotten-flesh.snbt b/src/test/resources/testdata/chat/sacks/gain-rotten-flesh.snbt index 924a558..d7b8b90 100644 --- a/src/test/resources/testdata/chat/sacks/gain-rotten-flesh.snbt +++ b/src/test/resources/testdata/chat/sacks/gain-rotten-flesh.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, color: "#FFAA00", extra: [ { diff --git a/src/test/resources/testdata/items/aspect-of-the-void.snbt b/src/test/resources/testdata/items/aspect-of-the-void.snbt index 180c069..9ffd385 100644 --- a/src/test/resources/testdata/items/aspect-of-the-void.snbt +++ b/src/test/resources/testdata/items/aspect-of-the-void.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:attribute_modifiers": { modifiers: [ diff --git a/src/test/resources/testdata/items/books/feather_falling.snbt b/src/test/resources/testdata/items/books/feather_falling.snbt index 1de4632..4a0b7c6 100644 --- a/src/test/resources/testdata/items/books/feather_falling.snbt +++ b/src/test/resources/testdata/items/books/feather_falling.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:attribute_modifiers": { modifiers: [ diff --git a/src/test/resources/testdata/items/diamond-pickaxe.snbt b/src/test/resources/testdata/items/diamond-pickaxe.snbt index cce12f9..aa5e590 100644 --- a/src/test/resources/testdata/items/diamond-pickaxe.snbt +++ b/src/test/resources/testdata/items/diamond-pickaxe.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:attribute_modifiers": { modifiers: [ diff --git a/src/test/resources/testdata/items/gemstone-gauntlet.snbt b/src/test/resources/testdata/items/gemstone-gauntlet.snbt index 92ce739..92bb806 100644 --- a/src/test/resources/testdata/items/gemstone-gauntlet.snbt +++ b/src/test/resources/testdata/items/gemstone-gauntlet.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:attribute_modifiers": { modifiers: [ diff --git a/src/test/resources/testdata/items/hyperion.snbt b/src/test/resources/testdata/items/hyperion.snbt index c57d457..f0025b9 100644 --- a/src/test/resources/testdata/items/hyperion.snbt +++ b/src/test/resources/testdata/items/hyperion.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:attribute_modifiers": { modifiers: [ diff --git a/src/test/resources/testdata/items/implosion-belt.snbt b/src/test/resources/testdata/items/implosion-belt.snbt index b73542d..875047d 100644 --- a/src/test/resources/testdata/items/implosion-belt.snbt +++ b/src/test/resources/testdata/items/implosion-belt.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:attribute_modifiers": { modifiers: [ diff --git a/src/test/resources/testdata/items/necron-boots.snbt b/src/test/resources/testdata/items/necron-boots.snbt index 35f8cf0..fd740ce 100644 --- a/src/test/resources/testdata/items/necron-boots.snbt +++ b/src/test/resources/testdata/items/necron-boots.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:attribute_modifiers": { modifiers: [ diff --git a/src/test/resources/testdata/items/pets/lion-item.snbt b/src/test/resources/testdata/items/pets/lion-item.snbt index 6e92685..c364032 100644 --- a/src/test/resources/testdata/items/pets/lion-item.snbt +++ b/src/test/resources/testdata/items/pets/lion-item.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:attribute_modifiers": { modifiers: [ diff --git a/src/test/resources/testdata/items/pets/mithril-golem-not-selected.snbt b/src/test/resources/testdata/items/pets/mithril-golem-not-selected.snbt index c0ef585..79f32c9 100644 --- a/src/test/resources/testdata/items/pets/mithril-golem-not-selected.snbt +++ b/src/test/resources/testdata/items/pets/mithril-golem-not-selected.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:custom_data": { id: "PET", diff --git a/src/test/resources/testdata/items/pets/rabbit-selected.snbt b/src/test/resources/testdata/items/pets/rabbit-selected.snbt index 48a6f6f..d4c7235 100644 --- a/src/test/resources/testdata/items/pets/rabbit-selected.snbt +++ b/src/test/resources/testdata/items/pets/rabbit-selected.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:custom_data": { id: "PET", diff --git a/src/test/resources/testdata/items/rune-in-sack.snbt b/src/test/resources/testdata/items/rune-in-sack.snbt index b15488a..4624c0f 100644 --- a/src/test/resources/testdata/items/rune-in-sack.snbt +++ b/src/test/resources/testdata/items/rune-in-sack.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:custom_data": { }, diff --git a/src/test/resources/testdata/items/titanium-drill.snbt b/src/test/resources/testdata/items/titanium-drill.snbt index e3b6819..e49c6b0 100644 --- a/src/test/resources/testdata/items/titanium-drill.snbt +++ b/src/test/resources/testdata/items/titanium-drill.snbt @@ -1,4 +1,7 @@ { + source: { + dataVersion: 4189, + }, components: { "minecraft:attribute_modifiers": { modifiers: [ diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt index dc3b109..462b1e1 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt @@ -3,6 +3,8 @@ package moe.nea.firmament.features.texturepack import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executor +import java.util.function.Function import net.fabricmc.loader.api.FabricLoader import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer @@ -19,8 +21,11 @@ import kotlinx.serialization.serializer import kotlin.jvm.optionals.getOrNull import net.minecraft.block.Block import net.minecraft.block.BlockState -import net.minecraft.client.render.model.BakedModel -import net.minecraft.client.util.ModelIdentifier +import net.minecraft.client.render.model.Baker +import net.minecraft.client.render.model.BlockStateModel +import net.minecraft.client.render.model.ReferencedModelsCollector +import net.minecraft.client.render.model.SimpleBlockStateModel +import net.minecraft.client.render.model.json.ModelVariant import net.minecraft.registry.RegistryKey import net.minecraft.registry.RegistryKeys import net.minecraft.resource.ResourceManager @@ -28,11 +33,13 @@ import net.minecraft.resource.SinglePreparationResourceReloader import net.minecraft.util.Identifier import net.minecraft.util.math.BlockPos import net.minecraft.util.profiler.Profiler +import net.minecraft.util.thread.AsyncHelper import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.EarlyResourceReloadEvent import moe.nea.firmament.events.FinalizeResourceManagerEvent import moe.nea.firmament.events.SkyblockServerUpdateEvent +import moe.nea.firmament.features.texturepack.CustomBlockTextures.createBakedModels import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger import moe.nea.firmament.util.IdentifierSerializer import moe.nea.firmament.util.MC @@ -43,249 +50,301 @@ import moe.nea.firmament.util.json.SingletonSerializableList object CustomBlockTextures { - @Serializable - data class CustomBlockOverride( - val modes: @Serializable(SingletonSerializableList::class) List<String>, - val area: List<Area>? = null, - val replacements: Map<Identifier, Replacement>, - ) - - @Serializable(with = Replacement.Serializer::class) - data class Replacement( - val block: Identifier, - val sound: Identifier?, - ) { - - @Transient - val blockModelIdentifier get() = ModelIdentifier(block.withPrefixedPath("block/"), "firmament") - - @Transient - val bakedModel: BakedModel by lazy(LazyThreadSafetyMode.NONE) { - MC.instance.bakedModelManager.getModel(blockModelIdentifier) - } - - @OptIn(ExperimentalSerializationApi::class) - @kotlinx.serialization.Serializer(Replacement::class) - object DefaultSerializer : KSerializer<Replacement> - - object Serializer : KSerializer<Replacement> { - val delegate = serializer<JsonElement>() - override val descriptor: SerialDescriptor - get() = delegate.descriptor - - override fun deserialize(decoder: Decoder): Replacement { - val jsonElement = decoder.decodeSerializableValue(delegate) - if (jsonElement is JsonPrimitive) { - require(jsonElement.isString) - return Replacement(Identifier.tryParse(jsonElement.content)!!, null) - } - return (decoder as JsonDecoder).json.decodeFromJsonElement(DefaultSerializer, jsonElement) - } - - override fun serialize(encoder: Encoder, value: Replacement) { - encoder.encodeSerializableValue(DefaultSerializer, value) - } - } - } - - @Serializable - data class Area( - val min: BlockPos, - val max: BlockPos, - ) { - @Transient - val realMin = BlockPos( - minOf(min.x, max.x), - minOf(min.y, max.y), - minOf(min.z, max.z), - ) - - @Transient - val realMax = BlockPos( - maxOf(min.x, max.x), - maxOf(min.y, max.y), - maxOf(min.z, max.z), - ) - - fun roughJoin(other: Area): Area { - return Area( - BlockPos( - minOf(realMin.x, other.realMin.x), - minOf(realMin.y, other.realMin.y), - minOf(realMin.z, other.realMin.z), - ), - BlockPos( - maxOf(realMax.x, other.realMax.x), - maxOf(realMax.y, other.realMax.y), - maxOf(realMax.z, other.realMax.z), - ) - ) - } - - fun contains(blockPos: BlockPos): Boolean { - return (blockPos.x in realMin.x..realMax.x) && - (blockPos.y in realMin.y..realMax.y) && - (blockPos.z in realMin.z..realMax.z) - } - } - - data class LocationReplacements( - val lookup: Map<Block, List<BlockReplacement>> - ) - - data class BlockReplacement( - val checks: List<Area>?, - val replacement: Replacement, - ) { - val roughCheck by lazy(LazyThreadSafetyMode.NONE) { - if (checks == null || checks.size < 3) return@lazy null - checks.reduce { acc, next -> acc.roughJoin(next) } - } - } - - data class BakedReplacements(val data: Map<SkyBlockIsland, LocationReplacements>) - - var allLocationReplacements: BakedReplacements = BakedReplacements(mapOf()) - var currentIslandReplacements: LocationReplacements? = null - - fun refreshReplacements() { - val location = SBData.skyblockLocation - val replacements = - if (CustomSkyBlockTextures.TConfig.enableBlockOverrides) location?.let(allLocationReplacements.data::get) - else null - val lastReplacements = currentIslandReplacements - currentIslandReplacements = replacements - if (lastReplacements != replacements) { - MC.nextTick { - MC.worldRenderer.chunks?.chunks?.forEach { - // false schedules rebuilds outside a 27 block radius to happen async - it.scheduleRebuild(false) - } - sodiumReloadTask?.run() - } - } - } - - private val sodiumReloadTask = runCatching { - val r = Class.forName("moe.nea.firmament.compat.sodium.SodiumChunkReloader") - .getConstructor() - .newInstance() as Runnable - r.run() - r - }.getOrElse { - if (FabricLoader.getInstance().isModLoaded("sodium")) - logger.error("Could not create sodium chunk reloader") - null - } - - - fun matchesPosition(replacement: BlockReplacement, blockPos: BlockPos?): Boolean { - if (blockPos == null) return true - val rc = replacement.roughCheck - if (rc != null && !rc.contains(blockPos)) return false - val areas = replacement.checks - if (areas != null && !areas.any { it.contains(blockPos) }) return false - return true - } - - @JvmStatic - fun getReplacementModel(block: BlockState, blockPos: BlockPos?): BakedModel? { - return getReplacement(block, blockPos)?.bakedModel - } - - @JvmStatic - fun getReplacement(block: BlockState, blockPos: BlockPos?): Replacement? { - if (isInFallback() && blockPos == null) { - return null - } - val replacements = currentIslandReplacements?.lookup?.get(block.block) ?: return null - for (replacement in replacements) { - if (replacement.checks == null || matchesPosition(replacement, blockPos)) - return replacement.replacement - } - return null - } - - - @Subscribe - fun onLocation(event: SkyblockServerUpdateEvent) { - refreshReplacements() - } - - @Volatile - var preparationFuture: CompletableFuture<BakedReplacements> = CompletableFuture.completedFuture(BakedReplacements( - mapOf())) - - val insideFallbackCall = ThreadLocal.withInitial { 0 } - - @JvmStatic - fun enterFallbackCall() { - insideFallbackCall.set(insideFallbackCall.get() + 1) - } - - fun isInFallback() = insideFallbackCall.get() > 0 - - @JvmStatic - fun exitFallbackCall() { - insideFallbackCall.set(insideFallbackCall.get() - 1) - } - - @Subscribe - fun onEarlyReload(event: EarlyResourceReloadEvent) { - preparationFuture = CompletableFuture - .supplyAsync( - { prepare(event.resourceManager) }, event.preparationExecutor) - } - - private fun prepare(manager: ResourceManager): BakedReplacements { - val resources = manager.findResources("overrides/blocks") { - it.namespace == "firmskyblock" && it.path.endsWith(".json") - } - val map = mutableMapOf<SkyBlockIsland, MutableMap<Block, MutableList<BlockReplacement>>>() - for ((file, resource) in resources) { - val json = - Firmament.tryDecodeJsonFromStream<CustomBlockOverride>(resource.inputStream) - .getOrElse { ex -> - logger.error("Failed to load block texture override at $file", ex) - continue - } - for (mode in json.modes) { - val island = SkyBlockIsland.forMode(mode) - val islandMpa = map.getOrPut(island, ::mutableMapOf) - for ((blockId, replacement) in json.replacements) { - val block = MC.defaultRegistries.getOrThrow(RegistryKeys.BLOCK) - .getOptional(RegistryKey.of(RegistryKeys.BLOCK, blockId)) - .getOrNull() - if (block == null) { - logger.error("Failed to load block texture override at ${file}: unknown block '$blockId'") - continue - } - val replacements = islandMpa.getOrPut(block.value(), ::mutableListOf) - replacements.add(BlockReplacement(json.area, replacement)) - } - } - } - - return BakedReplacements(map.mapValues { LocationReplacements(it.value) }) - } - - @JvmStatic - fun patchIndigo(orig: BakedModel, pos: BlockPos, state: BlockState): BakedModel { - return getReplacementModel(state, pos) ?: orig - } - - @Subscribe - fun onStart(event: FinalizeResourceManagerEvent) { - event.resourceManager.registerReloader(object : - SinglePreparationResourceReloader<BakedReplacements>() { - override fun prepare(manager: ResourceManager, profiler: Profiler): BakedReplacements { - return preparationFuture.join() - } - - override fun apply(prepared: BakedReplacements, manager: ResourceManager, profiler: Profiler?) { - allLocationReplacements = prepared - refreshReplacements() - } - }) - } + @Serializable + data class CustomBlockOverride( + val modes: @Serializable(SingletonSerializableList::class) List<String>, + val area: List<Area>? = null, + val replacements: Map<Identifier, Replacement>, + ) + + @Serializable(with = Replacement.Serializer::class) + data class Replacement( + val block: Identifier, + val sound: Identifier?, + ) { + + @Transient + val blockModelIdentifier get() = block.withPrefixedPath("block/") + + /** + * Guaranteed to be set after [BakedReplacements.modelBakingFuture] is complete. + */ + @Transient + lateinit var blockModel: BlockStateModel + + @OptIn(ExperimentalSerializationApi::class) + @kotlinx.serialization.Serializer(Replacement::class) + object DefaultSerializer : KSerializer<Replacement> + + object Serializer : KSerializer<Replacement> { + val delegate = serializer<JsonElement>() + override val descriptor: SerialDescriptor + get() = delegate.descriptor + + override fun deserialize(decoder: Decoder): Replacement { + val jsonElement = decoder.decodeSerializableValue(delegate) + if (jsonElement is JsonPrimitive) { + require(jsonElement.isString) + return Replacement(Identifier.tryParse(jsonElement.content)!!, null) + } + return (decoder as JsonDecoder).json.decodeFromJsonElement(DefaultSerializer, jsonElement) + } + + override fun serialize(encoder: Encoder, value: Replacement) { + encoder.encodeSerializableValue(DefaultSerializer, value) + } + } + } + + @Serializable + data class Area( + val min: BlockPos, + val max: BlockPos, + ) { + @Transient + val realMin = BlockPos( + minOf(min.x, max.x), + minOf(min.y, max.y), + minOf(min.z, max.z), + ) + + @Transient + val realMax = BlockPos( + maxOf(min.x, max.x), + maxOf(min.y, max.y), + maxOf(min.z, max.z), + ) + + fun roughJoin(other: Area): Area { + return Area( + BlockPos( + minOf(realMin.x, other.realMin.x), + minOf(realMin.y, other.realMin.y), + minOf(realMin.z, other.realMin.z), + ), + BlockPos( + maxOf(realMax.x, other.realMax.x), + maxOf(realMax.y, other.realMax.y), + maxOf(realMax.z, other.realMax.z), + ) + ) + } + + fun contains(blockPos: BlockPos): Boolean { + return (blockPos.x in realMin.x..realMax.x) && + (blockPos.y in realMin.y..realMax.y) && + (blockPos.z in realMin.z..realMax.z) + } + } + + data class LocationReplacements( + val lookup: Map<Block, List<BlockReplacement>> + ) + + data class BlockReplacement( + val checks: List<Area>?, + val replacement: Replacement, + ) { + val roughCheck by lazy(LazyThreadSafetyMode.NONE) { + if (checks == null || checks.size < 3) return@lazy null + checks.reduce { acc, next -> acc.roughJoin(next) } + } + } + + data class BakedReplacements(val data: Map<SkyBlockIsland, LocationReplacements>) { + /** + * Fulfilled by [createBakedModels] which is called during model baking. Once completed, all [Replacement.blockModel] will be set. + */ + val modelBakingFuture = CompletableFuture<Unit>() + + /** + * @returns a list of all [Replacement]s. + */ + fun collectAllReplacements(): Sequence<Replacement> { + return data.values.asSequence() + .flatMap { it.lookup.values } + .flatten() + .map { it.replacement } + } + } + + var allLocationReplacements: BakedReplacements = BakedReplacements(mapOf()) + var currentIslandReplacements: LocationReplacements? = null + + fun refreshReplacements() { + val location = SBData.skyblockLocation + val replacements = + if (CustomSkyBlockTextures.TConfig.enableBlockOverrides) location?.let(allLocationReplacements.data::get) + else null + val lastReplacements = currentIslandReplacements + currentIslandReplacements = replacements + if (lastReplacements != replacements) { + MC.nextTick { + MC.worldRenderer.chunks?.chunks?.forEach { + // false schedules rebuilds outside a 27 block radius to happen async + it.scheduleRebuild(false) + } + sodiumReloadTask?.run() + } + } + } + + private val sodiumReloadTask = runCatching { + val r = Class.forName("moe.nea.firmament.compat.sodium.SodiumChunkReloader") + .getConstructor() + .newInstance() as Runnable + r.run() + r + }.getOrElse { + if (FabricLoader.getInstance().isModLoaded("sodium")) + logger.error("Could not create sodium chunk reloader") + null + } + + + fun matchesPosition(replacement: BlockReplacement, blockPos: BlockPos?): Boolean { + if (blockPos == null) return true + val rc = replacement.roughCheck + if (rc != null && !rc.contains(blockPos)) return false + val areas = replacement.checks + if (areas != null && !areas.any { it.contains(blockPos) }) return false + return true + } + + @JvmStatic + fun getReplacementModel(block: BlockState, blockPos: BlockPos?): BlockStateModel? { + return getReplacement(block, blockPos)?.blockModel + } + + @JvmStatic + fun getReplacement(block: BlockState, blockPos: BlockPos?): Replacement? { + if (isInFallback() && blockPos == null) { + return null + } + val replacements = currentIslandReplacements?.lookup?.get(block.block) ?: return null + for (replacement in replacements) { + if (replacement.checks == null || matchesPosition(replacement, blockPos)) + return replacement.replacement + } + return null + } + + + @Subscribe + fun onLocation(event: SkyblockServerUpdateEvent) { + refreshReplacements() + } + + @Volatile + var preparationFuture: CompletableFuture<BakedReplacements> = CompletableFuture.completedFuture(BakedReplacements( + mapOf())) + + val insideFallbackCall = ThreadLocal.withInitial { 0 } + + @JvmStatic + fun enterFallbackCall() { + insideFallbackCall.set(insideFallbackCall.get() + 1) + } + + fun isInFallback() = insideFallbackCall.get() > 0 + + @JvmStatic + fun exitFallbackCall() { + insideFallbackCall.set(insideFallbackCall.get() - 1) + } + + @Subscribe + fun onEarlyReload(event: EarlyResourceReloadEvent) { + preparationFuture = CompletableFuture + .supplyAsync( + { prepare(event.resourceManager) }, event.preparationExecutor) + } + + private fun prepare(manager: ResourceManager): BakedReplacements { + val resources = manager.findResources("overrides/blocks") { + it.namespace == "firmskyblock" && it.path.endsWith(".json") + } + val map = mutableMapOf<SkyBlockIsland, MutableMap<Block, MutableList<BlockReplacement>>>() + for ((file, resource) in resources) { + val json = + Firmament.tryDecodeJsonFromStream<CustomBlockOverride>(resource.inputStream) + .getOrElse { ex -> + logger.error("Failed to load block texture override at $file", ex) + continue + } + for (mode in json.modes) { + val island = SkyBlockIsland.forMode(mode) + val islandMpa = map.getOrPut(island, ::mutableMapOf) + for ((blockId, replacement) in json.replacements) { + val block = MC.defaultRegistries.getOrThrow(RegistryKeys.BLOCK) + .getOptional(RegistryKey.of(RegistryKeys.BLOCK, blockId)) + .getOrNull() + if (block == null) { + logger.error("Failed to load block texture override at ${file}: unknown block '$blockId'") + continue + } + val replacements = islandMpa.getOrPut(block.value(), ::mutableListOf) + replacements.add(BlockReplacement(json.area, replacement)) + } + } + } + + return BakedReplacements(map.mapValues { LocationReplacements(it.value) }) + } + + @Subscribe + fun onStart(event: FinalizeResourceManagerEvent) { + event.resourceManager.registerReloader(object : + SinglePreparationResourceReloader<BakedReplacements>() { + override fun prepare(manager: ResourceManager, profiler: Profiler): BakedReplacements { + return preparationFuture.join().also { + it.modelBakingFuture.join() + } + } + + override fun apply(prepared: BakedReplacements, manager: ResourceManager, profiler: Profiler?) { + allLocationReplacements = prepared + refreshReplacements() + } + }) + } + + fun simpleBlockModel(blockId: Identifier): SimpleBlockStateModel.Unbaked { + // TODO: does this need to be shared between resolving and baking? I think not, but it would probably be wise to do so in the future. + return SimpleBlockStateModel.Unbaked( + ModelVariant(blockId) + ) + } + + /** + * Used by [moe.nea.firmament.init.SectionBuilderRiser] + */ + + @JvmStatic + fun patchIndigo(original: BlockStateModel, pos: BlockPos?, state: BlockState): BlockStateModel { + return getReplacementModel(state, pos) ?: original + } + + @JvmStatic + fun collectExtraModels(modelsCollector: ReferencedModelsCollector) { + preparationFuture.join().collectAllReplacements() + .forEach { modelsCollector.resolve(simpleBlockModel(it.blockModelIdentifier)) } + } + + @JvmStatic + fun createBakedModels(baker: Baker, executor: Executor): CompletableFuture<Void?> { + return preparationFuture.thenComposeAsync(Function { replacements -> + val byModel = replacements.collectAllReplacements().groupBy { it.blockModelIdentifier } + val modelBakingTask = AsyncHelper.mapValues(byModel, { blockId, replacements -> + val unbakedModel = SimpleBlockStateModel.Unbaked( + ModelVariant(blockId) + ) + val baked = unbakedModel.bake(baker) + replacements.forEach { + it.blockModel = baked + } + }, executor) + modelBakingTask.thenAcceptAsync { replacements.modelBakingFuture.complete(Unit) } + }, executor) + } } diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt index aafc85a..8a2bde5 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt @@ -81,20 +81,27 @@ object CustomGlobalArmorOverrides { null, Optional.of(RegistryKey.of(EquipmentAssetKeys.REGISTRY_KEY, model)), Optional.empty(), - Optional.empty(), false, false, false + Optional.empty(), + false, + false, + false, + false ) } + // TODO: BipedEntityRenderer.getEquippedStack create copies of itemstacks for rendering. This means this cache is essentially useless + // If i figure out how to circumvent this (maybe track the origin of those copied itemstacks in some sort of variable in the itemstack to track back the original instance) i should reenable this cache. + // Then also re add this to the cache clearing function val overrideCache = - WeakCache.memoize<ItemStack, EquipmentSlot, Optional<EquippableComponent>>("ArmorOverrides") { stack, slot -> - val id = stack.skyBlockId ?: return@memoize Optional.empty() - val override = overrides[id.neuItem] ?: return@memoize Optional.empty() + WeakCache.dontMemoize<ItemStack, EquipmentSlot, Optional<EquippableComponent>>("ArmorOverrides") { stack, slot -> + val id = stack.skyBlockId ?: return@dontMemoize Optional.empty() + val override = overrides[id.neuItem] ?: return@dontMemoize Optional.empty() for (suboverride in override.overrides) { if (suboverride.predicate.test(stack)) { - return@memoize resolveComponent(slot, suboverride.modelIdentifier).intoOptional() + return@dontMemoize resolveComponent(slot, suboverride.modelIdentifier).intoOptional() } } - return@memoize resolveComponent(slot, override.modelIdentifier).intoOptional() + return@dontMemoize resolveComponent(slot, override.modelIdentifier).intoOptional() } var overrides: Map<String, ArmorOverride> = mapOf() @@ -111,7 +118,7 @@ object CustomGlobalArmorOverrides { val equipmentLayers = layers.map { EquipmentModel.Layer( it.identifier, if (it.tint) { - Optional.of(EquipmentModel.Dyeable(Optional.empty())) + Optional.of(EquipmentModel.Dyeable(Optional.of(0xFFA06540.toInt()))) } else { Optional.empty() }, diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt index 20f1fb6..403e3bd 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt @@ -8,7 +8,6 @@ import org.slf4j.LoggerFactory import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers import kotlin.jvm.optionals.getOrNull -import net.minecraft.client.util.ModelIdentifier import net.minecraft.resource.ResourceManager import net.minecraft.resource.SinglePreparationResourceReloader import net.minecraft.text.Text diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt index 4529d1d..1da840d 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt @@ -17,12 +17,14 @@ import moe.nea.firmament.features.texturepack.predicates.AndPredicate import moe.nea.firmament.features.texturepack.predicates.CastPredicate import moe.nea.firmament.features.texturepack.predicates.DisplayNamePredicate import moe.nea.firmament.features.texturepack.predicates.ExtraAttributesPredicate +import moe.nea.firmament.features.texturepack.predicates.GenericComponentPredicate import moe.nea.firmament.features.texturepack.predicates.ItemPredicate import moe.nea.firmament.features.texturepack.predicates.LorePredicate import moe.nea.firmament.features.texturepack.predicates.NotPredicate import moe.nea.firmament.features.texturepack.predicates.OrPredicate import moe.nea.firmament.features.texturepack.predicates.PetPredicate import moe.nea.firmament.features.texturepack.predicates.PullingPredicate +import moe.nea.firmament.features.texturepack.predicates.SkullPredicate import moe.nea.firmament.util.json.KJsonOps object CustomModelOverrideParser { @@ -63,6 +65,8 @@ object CustomModelOverrideParser { registerPredicateParser("item", ItemPredicate.Parser) registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser) registerPredicateParser("pet", PetPredicate.Parser) + registerPredicateParser("component", GenericComponentPredicate.Parser) + registerPredicateParser("skull", SkullPredicate.Parser) } private val neverPredicate = listOf( @@ -110,6 +114,10 @@ object CustomModelOverrideParser { Firmament.identifier("predicates/legacy"), PredicateModel.Unbaked.CODEC ) + ItemModelTypes.ID_MAPPER.put( + Firmament.identifier("head_model"), + HeadModelChooser.Unbaked.CODEC + ) } } diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt index bef52d2..cf2a232 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt @@ -45,7 +45,7 @@ object CustomSkyBlockTextures : FirmamentFeature { listOf( skullTextureCache.cache, CustomItemModelEvent.cache.cache, - CustomGlobalArmorOverrides.overrideCache.cache + // TODO: re-add this once i figure out how to make the cache useful again CustomGlobalArmorOverrides.overrideCache.cache ) } diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt index 6cef4ca..e020d66 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt @@ -1,8 +1,10 @@ package moe.nea.firmament.features.texturepack +import kotlinx.serialization.Serializable import net.minecraft.entity.LivingEntity import net.minecraft.item.ItemStack +@Serializable(with = FirmamentRootPredicateSerializer::class) interface FirmamentModelPredicate { fun test(stack: ItemStack, holder: LivingEntity?): Boolean = test(stack) fun test(stack: ItemStack): Boolean = test(stack, null) diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/HeadModelChooser.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/HeadModelChooser.kt new file mode 100644 index 0000000..3e8cc4e --- /dev/null +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/HeadModelChooser.kt @@ -0,0 +1,90 @@ +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonObject +import com.mojang.serialization.MapCodec +import com.mojang.serialization.codecs.RecordCodecBuilder +import net.minecraft.client.item.ItemModelManager +import net.minecraft.client.render.item.ItemRenderState +import net.minecraft.client.render.item.model.BasicItemModel +import net.minecraft.client.render.item.model.ItemModel +import net.minecraft.client.render.item.model.ItemModelTypes +import net.minecraft.client.render.model.ResolvableModel +import net.minecraft.client.world.ClientWorld +import net.minecraft.entity.LivingEntity +import net.minecraft.item.ItemDisplayContext +import net.minecraft.item.ItemStack +import net.minecraft.util.Identifier + +object HeadModelChooser { + val IS_CHOOSING_HEAD_MODEL = ThreadLocal.withInitial { false } + + interface HasExplicitHeadModelMarker { + fun markExplicitHead_Firmament() + fun isExplicitHeadModel_Firmament(): Boolean + companion object{ + @JvmStatic + fun cast(state: ItemRenderState) = state as HasExplicitHeadModelMarker + } + } + + data class Baked(val head: ItemModel, val regular: ItemModel) : ItemModel { + override fun update( + state: ItemRenderState, + stack: ItemStack?, + resolver: ItemModelManager?, + displayContext: ItemDisplayContext, + world: ClientWorld?, + user: LivingEntity?, + seed: Int + ) { + val instance = + if (IS_CHOOSING_HEAD_MODEL.get()) { + HasExplicitHeadModelMarker.cast(state).markExplicitHead_Firmament() + head + } else { + regular + } + instance.update(state, stack, resolver, displayContext, world, user, seed) + } + } + + data class Unbaked( + val head: ItemModel.Unbaked, + val regular: ItemModel.Unbaked, + ) : ItemModel.Unbaked { + override fun getCodec(): MapCodec<out ItemModel.Unbaked> { + return CODEC + } + + override fun bake(context: ItemModel.BakeContext): ItemModel { + return Baked( + head.bake(context), + regular.bake(context) + ) + } + + override fun resolve(resolver: ResolvableModel.Resolver) { + head.resolve(resolver) + regular.resolve(resolver) + } + + companion object { + @JvmStatic + fun fromLegacyJson(jsonObject: JsonObject, unbakedModel: ItemModel.Unbaked): ItemModel.Unbaked { + val model = jsonObject["firmament:head_model"] ?: return unbakedModel + val modelUrl = model.asJsonPrimitive.asString + val headModel = BasicItemModel.Unbaked(Identifier.of(modelUrl), listOf()) + return Unbaked(headModel, unbakedModel) + } + + val CODEC = RecordCodecBuilder.mapCodec { + it.group( + ItemModelTypes.CODEC.fieldOf("head") + .forGetter(Unbaked::head), + ItemModelTypes.CODEC.fieldOf("regular") + .forGetter(Unbaked::regular), + ).apply(it, ::Unbaked) + } + } + } +} diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt index 0edad4c..e6b5bcf 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt @@ -9,12 +9,11 @@ import net.minecraft.client.render.item.ItemRenderState import net.minecraft.client.render.item.model.BasicItemModel import net.minecraft.client.render.item.model.ItemModel import net.minecraft.client.render.item.model.ItemModelTypes -import net.minecraft.client.render.item.tint.TintSource import net.minecraft.client.render.model.ResolvableModel import net.minecraft.client.world.ClientWorld import net.minecraft.entity.LivingEntity +import net.minecraft.item.ItemDisplayContext import net.minecraft.item.ItemStack -import net.minecraft.item.ModelTransformationMode import net.minecraft.util.Identifier import moe.nea.firmament.features.texturepack.predicates.AndPredicate @@ -29,10 +28,10 @@ class PredicateModel { ) override fun update( - state: ItemRenderState, + state: ItemRenderState?, stack: ItemStack, - resolver: ItemModelManager, - transformationMode: ModelTransformationMode, + resolver: ItemModelManager?, + displayContext: ItemDisplayContext?, world: ClientWorld?, user: LivingEntity?, seed: Int @@ -42,7 +41,7 @@ class PredicateModel { .findLast { it.predicate.test(stack, user) } ?.model ?: fallback - model.update(state, stack, resolver, transformationMode, world, user, seed) + model.update(state, stack, resolver, displayContext, world, user, seed) } } diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt index 2b13284..dd28d9f 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt @@ -13,6 +13,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlin.jvm.optionals.getOrNull import net.minecraft.nbt.NbtString import net.minecraft.text.Text import moe.nea.firmament.util.MC @@ -26,7 +27,7 @@ interface StringMatcher { } fun matches(nbt: NbtString): Boolean { - val string = nbt.asString() + val string = nbt.value val jsonStart = string.indexOf('{') val stringStart = string.indexOf('"') val isString = stringStart >= 0 && string.subSequence(0, stringStart).isBlank() diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt index 2b79c1a..321f87c 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt @@ -16,7 +16,7 @@ class CastPredicate : FirmamentModelPredicate { } override fun test(stack: ItemStack, holder: LivingEntity?): Boolean { - return (holder as? PlayerEntity)?.fishHook != null && holder.activeItem === stack + return (holder as? PlayerEntity)?.fishHook != null && holder.mainHandStack === stack } override fun test(stack: ItemStack): Boolean { diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt index 3c8023d..8115739 100644 --- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt @@ -1,215 +1,220 @@ package moe.nea.firmament.features.texturepack.predicates -import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive +import kotlin.jvm.optionals.getOrDefault +import kotlin.jvm.optionals.getOrNull import moe.nea.firmament.features.texturepack.FirmamentModelPredicate import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser import moe.nea.firmament.features.texturepack.StringMatcher import net.minecraft.item.ItemStack import net.minecraft.nbt.NbtByte -import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtDouble import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtFloat import net.minecraft.nbt.NbtInt -import net.minecraft.nbt.NbtList import net.minecraft.nbt.NbtLong import net.minecraft.nbt.NbtShort -import net.minecraft.nbt.NbtString import moe.nea.firmament.util.extraAttributes +import moe.nea.firmament.util.mc.NbtPrism fun interface NbtMatcher { - fun matches(nbt: NbtElement): Boolean - - object Parser { - fun parse(jsonElement: JsonElement): NbtMatcher? { - if (jsonElement is JsonPrimitive) { - if (jsonElement.isString) { - val string = jsonElement.asString - return MatchStringExact(string) - } - if (jsonElement.isNumber) { - return MatchNumberExact(jsonElement.asLong) //TODO: parse generic number - } - } - if (jsonElement is JsonObject) { - var encounteredParser: NbtMatcher? = null - for (entry in ExclusiveParserType.entries) { - val data = jsonElement[entry.key] ?: continue - if (encounteredParser != null) { - // TODO: warn - return null - } - encounteredParser = entry.parse(data) ?: return null - } - return encounteredParser - } - return null - } - - enum class ExclusiveParserType(val key: String) { - STRING("string") { - override fun parse(element: JsonElement): NbtMatcher? { - return MatchString(StringMatcher.parse(element)) - } - }, - INT("int") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asInt }, - { (it as? NbtInt)?.intValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - FLOAT("float") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asFloat }, - { (it as? NbtFloat)?.floatValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - DOUBLE("double") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asDouble }, - { (it as? NbtDouble)?.doubleValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - LONG("long") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asLong }, - { (it as? NbtLong)?.longValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - SHORT("short") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asShort }, - { (it as? NbtShort)?.shortValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - BYTE("byte") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asByte }, - { (it as? NbtByte)?.byteValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - ; - - abstract fun parse(element: JsonElement): NbtMatcher? - } - - enum class Comparison { - LESS_THAN, EQUAL, GREATER - } - - inline fun <T : Any> parseGenericNumber( - jsonElement: JsonElement, - primitiveExtractor: (JsonPrimitive) -> T?, - crossinline nbtExtractor: (NbtElement) -> T?, - crossinline compare: (T, T) -> Comparison - ): NbtMatcher? { - if (jsonElement is JsonPrimitive) { - val expected = primitiveExtractor(jsonElement) ?: return null - return NbtMatcher { - val actual = nbtExtractor(it) ?: return@NbtMatcher false - compare(actual, expected) == Comparison.EQUAL - } - } - if (jsonElement is JsonObject) { - val minElement = jsonElement.getAsJsonPrimitive("min") - val min = if (minElement != null) primitiveExtractor(minElement) ?: return null else null - val minExclusive = jsonElement.get("minExclusive")?.asBoolean ?: false - val maxElement = jsonElement.getAsJsonPrimitive("max") - val max = if (maxElement != null) primitiveExtractor(maxElement) ?: return null else null - val maxExclusive = jsonElement.get("maxExclusive")?.asBoolean ?: true - if (min == null && max == null) return null - return NbtMatcher { - val actual = nbtExtractor(it) ?: return@NbtMatcher false - if (max != null) { - val comp = compare(actual, max) - if (comp == Comparison.GREATER) return@NbtMatcher false - if (comp == Comparison.EQUAL && maxExclusive) return@NbtMatcher false - } - if (min != null) { - val comp = compare(actual, min) - if (comp == Comparison.LESS_THAN) return@NbtMatcher false - if (comp == Comparison.EQUAL && minExclusive) return@NbtMatcher false - } - return@NbtMatcher true - } - } - return null - - } - } - - class MatchNumberExact(val number: Long) : NbtMatcher { - override fun matches(nbt: NbtElement): Boolean { - return when (nbt) { - is NbtByte -> nbt.byteValue().toLong() == number - is NbtInt -> nbt.intValue().toLong() == number - is NbtShort -> nbt.shortValue().toLong() == number - is NbtLong -> nbt.longValue().toLong() == number - else -> false - } - } - - } - - class MatchStringExact(val string: String) : NbtMatcher { - override fun matches(nbt: NbtElement): Boolean { - return nbt is NbtString && nbt.asString() == string - } - - override fun toString(): String { - return "MatchNbtStringExactly($string)" - } - } - - class MatchString(val string: StringMatcher) : NbtMatcher { - override fun matches(nbt: NbtElement): Boolean { - return nbt is NbtString && string.matches(nbt.asString()) - } - - override fun toString(): String { - return "MatchNbtString($string)" - } - } + fun matches(nbt: NbtElement): Boolean + + object Parser { + fun parse(jsonElement: JsonElement): NbtMatcher? { + if (jsonElement is JsonPrimitive) { + if (jsonElement.isString) { + val string = jsonElement.asString + return MatchStringExact(string) + } + if (jsonElement.isNumber) { + return MatchNumberExact(jsonElement.asLong) // TODO: parse generic number + } + } + if (jsonElement is JsonObject) { + var encounteredParser: NbtMatcher? = null + for (entry in ExclusiveParserType.entries) { + val data = jsonElement[entry.key] ?: continue + if (encounteredParser != null) { + // TODO: warn + return null + } + encounteredParser = entry.parse(data) ?: return null + } + return encounteredParser + } + return null + } + + enum class ExclusiveParserType(val key: String) { + STRING("string") { + override fun parse(element: JsonElement): NbtMatcher? { + return MatchString(StringMatcher.parse(element)) + } + }, + INT("int") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber( + element, + { it.asInt }, + { (it as? NbtInt)?.intValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + FLOAT("float") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber( + element, + { it.asFloat }, + { (it as? NbtFloat)?.floatValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + DOUBLE("double") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber( + element, + { it.asDouble }, + { (it as? NbtDouble)?.doubleValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + LONG("long") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber( + element, + { it.asLong }, + { (it as? NbtLong)?.longValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + SHORT("short") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber( + element, + { it.asShort }, + { (it as? NbtShort)?.shortValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + BYTE("byte") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber( + element, + { it.asByte }, + { (it as? NbtByte)?.byteValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + ; + + abstract fun parse(element: JsonElement): NbtMatcher? + } + + enum class Comparison { + LESS_THAN, EQUAL, GREATER + } + + inline fun <T : Any> parseGenericNumber( + jsonElement: JsonElement, + primitiveExtractor: (JsonPrimitive) -> T?, + crossinline nbtExtractor: (NbtElement) -> T?, + crossinline compare: (T, T) -> Comparison + ): NbtMatcher? { + if (jsonElement is JsonPrimitive) { + val expected = primitiveExtractor(jsonElement) ?: return null + return NbtMatcher { + val actual = nbtExtractor(it) ?: return@NbtMatcher false + compare(actual, expected) == Comparison.EQUAL + } + } + if (jsonElement is JsonObject) { + val minElement = jsonElement.getAsJsonPrimitive("min") + val min = if (minElement != null) primitiveExtractor(minElement) ?: return null else null + val minExclusive = jsonElement.get("minExclusive")?.asBoolean ?: false + val maxElement = jsonElement.getAsJsonPrimitive("max") + val max = if (maxElement != null) primitiveExtractor(maxElement) ?: return null else null + val maxExclusive = jsonElement.get("maxExclusive")?.asBoolean ?: true + if (min == null && max == null) return null + return NbtMatcher { + val actual = nbtExtractor(it) ?: return@NbtMatcher false + if (max != null) { + val comp = compare(actual, max) + if (comp == Comparison.GREATER) return@NbtMatcher false + if (comp == Comparison.EQUAL && maxExclusive) return@NbtMatcher false + } + if (min != null) { + val comp = compare(actual, min) + if (comp == Comparison.LESS_THAN) return@NbtMatcher false + if (comp == Comparison.EQUAL && minExclusive) return@NbtMatcher false + } + return@NbtMatcher true + } + } + return null + + } + } + + class MatchNumberExact(val number: Long) : NbtMatcher { + override fun matches(nbt: NbtElement): Boolean { + return when (nbt) { + is NbtByte -> nbt.byteValue().toLong() == number + is NbtInt -> nbt.intValue().toLong() == number + is NbtShort -> nbt.shortValue().toLong() == number + is NbtLong -> nbt.longValue().toLong() == number + else -> false + } + } + + } + + class MatchStringExact(val string: String) : NbtMatcher { + override fun matches(nbt: NbtElement): Boolean { + return nbt.asString().getOrNull() == string + } + + override fun toString(): String { + return "MatchNbtStringExactly($string)" + } + } + + class MatchString(val string: StringMatcher) : NbtMatcher { + override fun matches(nbt: NbtElement): Boolean { + return nbt.asString().map(string::matches).getOrDefault(false) + } + + override fun toString(): String { + return "MatchNbtString($string)" + } + } } data class ExtraAttributesPredicate( @@ -217,55 +222,20 @@ data class ExtraAttributesPredicate( val matcher: NbtMatcher, ) : FirmamentModelPredicate { - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { - if (jsonElement !is JsonObject) return null - val path = jsonElement.get("path") ?: return null - val pathSegments = if (path is JsonArray) { - path.map { (it as JsonPrimitive).asString } - } else if (path is JsonPrimitive && path.isString) { - path.asString.split(".") - } else return null - val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement) - ?: return null - return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher) - } - } - - override fun test(stack: ItemStack): Boolean { - return path.access(stack.extraAttributes) - .any { matcher.matches(it) } - } + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { + if (jsonElement !is JsonObject) return null + val path = jsonElement.get("path") ?: return null + val prism = NbtPrism.fromElement(path) ?: return null + val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement) + ?: return null + return ExtraAttributesPredicate(prism, matcher) + } + } + + override fun test(stack: ItemStack): Boolean { + return path.access(stack.extraAttributes) + .any { matcher.matches(it) } + } } -class NbtPrism(val path: List<String>) { - override fun toString(): String { - return "Prism($path)" - } - fun access(root: NbtElement): Collection<NbtElement> { - var rootSet = mutableListOf(root) - var switch = mutableListOf<NbtElement>() - for (pathSegment in path) { - if (pathSegment == ".") continue - for (element in rootSet) { - if (element is NbtList) { - if (pathSegment == "*") - switch.addAll(element) - val index = pathSegment.toIntOrNull() ?: continue - if (index !in element.indices) continue - switch.add(element[index]) - } - if (element is NbtCompound) { - if (pathSegment == "*") - element.keys.mapTo(switch) { element.get(it)!! } - switch.add(element.get(pathSegment) ?: continue) - } - } - val temp = switch - switch = rootSet - rootSet = temp - switch.clear() - } - return rootSet - } -} diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/GenericComponentPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/GenericComponentPredicate.kt new file mode 100644 index 0000000..71392ef --- /dev/null +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/GenericComponentPredicate.kt @@ -0,0 +1,58 @@ +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.mojang.serialization.Codec +import kotlin.jvm.optionals.getOrNull +import net.minecraft.component.ComponentType +import net.minecraft.component.type.NbtComponent +import net.minecraft.entity.LivingEntity +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtOps +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.util.Identifier +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.mc.NbtPrism + +data class GenericComponentPredicate<T>( + val componentType: ComponentType<T>, + val codec: Codec<T>, + val path: NbtPrism, + val matcher: NbtMatcher, +) : FirmamentModelPredicate { + constructor(componentType: ComponentType<T>, path: NbtPrism, matcher: NbtMatcher) + : this(componentType, componentType.codecOrThrow, path, matcher) + + override fun test(stack: ItemStack, holder: LivingEntity?): Boolean { + val component = stack.get(componentType) ?: return false + // TODO: cache this + val nbt = + if (component is NbtComponent) component.nbt + else codec.encodeStart(NbtOps.INSTANCE, component) + .resultOrPartial().getOrNull() ?: return false + return path.access(nbt).any { matcher.matches(it) } + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): GenericComponentPredicate<*>? { + if (jsonElement !is JsonObject) return null + val path = jsonElement.get("path") ?: return null + val prism = NbtPrism.fromElement(path) ?: return null + val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement) + ?: return null + val component = MC.currentOrDefaultRegistries + .getOrThrow(RegistryKeys.DATA_COMPONENT_TYPE) + .getOrThrow( + RegistryKey.of( + RegistryKeys.DATA_COMPONENT_TYPE, + Identifier.of(jsonElement.get("component").asString) + ) + ).value() + return GenericComponentPredicate(component, prism, matcher) + } + } + +} diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/SkullPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/SkullPredicate.kt new file mode 100644 index 0000000..416e86c --- /dev/null +++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/SkullPredicate.kt @@ -0,0 +1,63 @@ +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonElement +import com.mojang.authlib.minecraft.MinecraftProfileTexture +import java.util.UUID +import kotlin.jvm.optionals.getOrNull +import net.minecraft.component.DataComponentTypes +import net.minecraft.entity.LivingEntity +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import moe.nea.firmament.features.texturepack.StringMatcher +import moe.nea.firmament.util.mc.decodeProfileTextureProperty +import moe.nea.firmament.util.parsePotentiallyDashlessUUID + +class SkullPredicate( + val profileId: UUID?, + val textureProfileId: UUID?, + val skinUrl: StringMatcher?, + val textureValue: StringMatcher?, +) : FirmamentModelPredicate { + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { + val obj = jsonElement.asJsonObject + val profileId = obj.getAsJsonPrimitive("profileId") + ?.asString?.let(::parsePotentiallyDashlessUUID) + val textureProfileId = obj.getAsJsonPrimitive("textureProfileId") + ?.asString?.let(::parsePotentiallyDashlessUUID) + val textureValue = obj.get("textureValue")?.let(StringMatcher::parse) + val skinUrl = obj.get("skinUrl")?.let(StringMatcher::parse) + return SkullPredicate(profileId, textureProfileId, skinUrl, textureValue) + } + } + + override fun test(stack: ItemStack, holder: LivingEntity?): Boolean { + if (!stack.isOf(Items.PLAYER_HEAD)) return false + val profile = stack.get(DataComponentTypes.PROFILE) ?: return false + val textureProperty = profile.properties["textures"].firstOrNull() + val textureMode = lazy(LazyThreadSafetyMode.NONE) { + decodeProfileTextureProperty(textureProperty ?: return@lazy null) + } + when { + profileId != null + && profileId != profile.id.getOrNull() -> + return false + + textureValue != null + && !textureValue.matches(textureProperty?.value ?: "") -> + return false + + skinUrl != null + && !skinUrl.matches(textureMode.value?.textures?.get(MinecraftProfileTexture.Type.SKIN)?.url ?: "") -> + return false + + textureProfileId != null + && textureProfileId != textureMode.value?.profileId -> + return false + + else -> return true + } + } +} diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/BuildExtraBlockStateModels.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/BuildExtraBlockStateModels.java new file mode 100644 index 0000000..6b3c929 --- /dev/null +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/BuildExtraBlockStateModels.java @@ -0,0 +1,24 @@ +package moe.nea.firmament.mixins.custommodels; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.llamalad7.mixinextras.sugar.Local; +import moe.nea.firmament.features.texturepack.CustomBlockTextures; +import net.minecraft.client.render.model.Baker; +import net.minecraft.client.render.model.ModelBaker; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@Mixin(ModelBaker.class) +public class BuildExtraBlockStateModels { + @ModifyReturnValue(method = "bake", at = @At("RETURN")) + private CompletableFuture<ModelBaker.BakedModels> injectMoreBlockModels(CompletableFuture<ModelBaker.BakedModels> original, @Local ModelBaker.BakerImpl baker, @Local(argsOnly = true) Executor executor) { + Baker b = baker; + return original.thenCombine( + CustomBlockTextures.createBakedModels(b, executor), + (a, _void) -> a + ); + } +} diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/InsertExtraBlockModelDependencies.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/InsertExtraBlockModelDependencies.java new file mode 100644 index 0000000..91779e7 --- /dev/null +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/InsertExtraBlockModelDependencies.java @@ -0,0 +1,28 @@ +package moe.nea.firmament.mixins.custommodels; + +import com.llamalad7.mixinextras.sugar.Local; +import moe.nea.firmament.features.texturepack.CustomBlockTextures; +import net.minecraft.client.item.ItemAssetsLoader; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.render.model.BlockStatesLoader; +import net.minecraft.client.render.model.ReferencedModelsCollector; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.util.Identifier; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Map; + +@Mixin(BakedModelManager.class) +public class InsertExtraBlockModelDependencies { + @Inject(method = "collect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/ReferencedModelsCollector;addSpecialModel(Lnet/minecraft/util/Identifier;Lnet/minecraft/client/render/model/UnbakedModel;)V", shift = At.Shift.AFTER)) + private static void insertExtraModels( + Map<Identifier, UnbakedModel> modelMap, + BlockStatesLoader.LoadedModels stateDefinition, + ItemAssetsLoader.Result result, + CallbackInfoReturnable cir, @Local ReferencedModelsCollector modelsCollector) { + CustomBlockTextures.collectExtraModels(modelsCollector); + } +} diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ItemRenderStateExtraInfo.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ItemRenderStateExtraInfo.java new file mode 100644 index 0000000..2872dd1 --- /dev/null +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ItemRenderStateExtraInfo.java @@ -0,0 +1,28 @@ +package moe.nea.firmament.mixins.custommodels; + +import moe.nea.firmament.features.texturepack.HeadModelChooser; +import net.minecraft.client.render.item.ItemRenderState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ItemRenderState.class) +public class ItemRenderStateExtraInfo implements HeadModelChooser.HasExplicitHeadModelMarker { + boolean hasExplicitHead_firmament = false; + + @Inject(method = "clear", at = @At("HEAD")) + private void clear(CallbackInfo ci) { + hasExplicitHead_firmament = false; + } + + @Override + public void markExplicitHead_Firmament() { + hasExplicitHead_firmament = true; + } + + @Override + public boolean isExplicitHeadModel_Firmament() { + return hasExplicitHead_firmament; + } +} diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java index 81ea6cd..951e3be 100644 --- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java @@ -1,23 +1,22 @@ package moe.nea.firmament.mixins.custommodels; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides; import net.minecraft.client.render.entity.equipment.EquipmentModel; import net.minecraft.client.render.entity.equipment.EquipmentModelLoader; -import net.minecraft.client.render.entity.equipment.EquipmentRenderer; import net.minecraft.item.equipment.EquipmentAsset; import net.minecraft.registry.RegistryKey; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; // TODO: auto import legacy models, maybe!!! in a later patch tho -@Mixin(EquipmentRenderer.class) +@Mixin(EquipmentModelLoader.class) public class PatchLegacyArmorLayerSupport { - @WrapOperation(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/equipment/EquipmentModelLoader;get(Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/client/render/entity/equipment/EquipmentModel;")) - private EquipmentModel patchModelLayers(EquipmentModelLoader instance, RegistryKey<EquipmentAsset> assetKey, Operation<EquipmentModel> original) { + @Inject(method = "get", at = @At(value = "HEAD"), cancellable = true) + private void patchModelLayers(RegistryKey<EquipmentAsset> assetKey, CallbackInfoReturnable<EquipmentModel> cir) { var modelOverride = CustomGlobalArmorOverrides.overrideArmorLayer(assetKey.getValue()); - if (modelOverride != null) return modelOverride; - return original.call(instance, assetKey); + if (modelOverride != null) + cir.setReturnValue(modelOverride); } } diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java index f829da0..0fb6bf8 100644 --- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java @@ -26,7 +26,6 @@ public class PatchLegacyTexturePathsIntoArmorLayers { // legacy format: "assets/{identifier.namespace}/textures/models/armor/{identifier.path}_layer_{isLegs ? 2 : 1}{suffix}.png" // suffix is sadly not available to us here. this means leather armor will look a bit shite var legacyIdentifier = this.textureId.withPath((textureName) -> { - String var10000 = layerType.asString(); return "textures/models/armor/" + textureName + "_layer_" + (layerType == EquipmentModel.LayerType.HUMANOID_LEGGINGS ? 2 : 1) + ".png"; diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java index 711b2af..8d2ba38 100644 --- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java @@ -5,34 +5,33 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; import moe.nea.firmament.features.texturepack.CustomBlockTextures; import net.minecraft.block.BlockState; -import net.minecraft.client.render.block.BlockModels; import net.minecraft.client.render.block.BlockRenderManager; -import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.chunk.SectionBuilder; +import net.minecraft.client.render.model.BlockStateModel; import net.minecraft.util.math.BlockPos; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -@Mixin(BlockRenderManager.class) +@Mixin(SectionBuilder.class) public class ReplaceBlockRenderManagerBlockModel { - @WrapOperation(method = "renderBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockRenderManager;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;")) - private BakedModel replaceModelInRenderBlock( - BlockRenderManager instance, BlockState state, Operation<BakedModel> original, @Local(argsOnly = true) BlockPos pos) { - var replacement = CustomBlockTextures.getReplacementModel(state, pos); - if (replacement != null) return replacement; - CustomBlockTextures.enterFallbackCall(); - var fallback = original.call(instance, state); - CustomBlockTextures.exitFallbackCall(); - return fallback; - } - - @WrapOperation(method = "renderDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;")) - private BakedModel replaceModelInRenderDamage( - BlockModels instance, BlockState state, Operation<BakedModel> original, @Local(argsOnly = true) BlockPos pos) { - var replacement = CustomBlockTextures.getReplacementModel(state, pos); - if (replacement != null) return replacement; - CustomBlockTextures.enterFallbackCall(); - var fallback = original.call(instance, state); - CustomBlockTextures.exitFallbackCall(); - return fallback; - } + @WrapOperation(method = "build", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockRenderManager;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BlockStateModel;")) + private BlockStateModel replaceModelInRenderBlock(BlockRenderManager instance, BlockState state, Operation<BlockStateModel> original, @Local(ordinal = 2) BlockPos pos) { + var replacement = CustomBlockTextures.getReplacementModel(state, pos); + if (replacement != null) return replacement; + CustomBlockTextures.enterFallbackCall(); + var fallback = original.call(instance, state); + CustomBlockTextures.exitFallbackCall(); + return fallback; + } +//TODO: cover renderDamage model +// @WrapOperation(method = "renderDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;")) +// private BakedModel replaceModelInRenderDamage( +// BlockModels instance, BlockState state, Operation<BakedModel> original, @Local(argsOnly = true) BlockPos pos) { +// var replacement = CustomBlockTextures.getReplacementModel(state, pos); +// if (replacement != null) return replacement; +// CustomBlockTextures.enterFallbackCall(); +// var fallback = original.call(instance, state); +// CustomBlockTextures.exitFallbackCall(); +// return fallback; +// } } diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java index 53ab74a..455fbf1 100644 --- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java @@ -3,7 +3,7 @@ package moe.nea.firmament.mixins.custommodels; import moe.nea.firmament.features.texturepack.CustomBlockTextures; import net.minecraft.block.BlockState; import net.minecraft.client.render.block.BlockModels; -import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BlockStateModel; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; public class ReplaceFallbackBlockModel { // TODO: add check to BlockDustParticle @Inject(method = "getModel", at = @At("HEAD"), cancellable = true) - private void getModel(BlockState state, CallbackInfoReturnable<BakedModel> cir) { + private void getModel(BlockState state, CallbackInfoReturnable<BlockStateModel> cir) { var replacement = CustomBlockTextures.getReplacementModel(state, null); if (replacement != null) cir.setReturnValue(replacement); diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceHeadModel.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceHeadModel.java new file mode 100644 index 0000000..f445f02 --- /dev/null +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceHeadModel.java @@ -0,0 +1,51 @@ +package moe.nea.firmament.mixins.custommodels; + +import moe.nea.firmament.features.texturepack.HeadModelChooser; +import net.minecraft.client.item.ItemModelManager; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.render.entity.state.LivingEntityRenderState; +import net.minecraft.client.render.item.ItemRenderState; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemDisplayContext; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(LivingEntityRenderer.class) +public class ReplaceHeadModel<T extends LivingEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>> { + @Shadow + @Final + protected ItemModelManager itemModelResolver; + + @Unique + private ItemRenderState tempRenderState = new ItemRenderState(); + + @Inject( + method = "updateRenderState(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;F)V", + at = @At("TAIL") + ) + private void replaceHeadModel( + T livingEntity, S livingEntityRenderState, float f, CallbackInfo ci + ) { + var headItemStack = livingEntity.getEquippedStack(EquipmentSlot.HEAD); + + HeadModelChooser.INSTANCE.getIS_CHOOSING_HEAD_MODEL().set(true); + tempRenderState.clear(); + this.itemModelResolver.updateForLivingEntity(tempRenderState, headItemStack, ItemDisplayContext.HEAD, livingEntity); + HeadModelChooser.INSTANCE.getIS_CHOOSING_HEAD_MODEL().set(false); + + if (HeadModelChooser.HasExplicitHeadModelMarker.cast(tempRenderState) + .isExplicitHeadModel_Firmament()) { + livingEntityRenderState.wearingSkullType = null; + var temp = livingEntityRenderState.headItemRenderState; + livingEntityRenderState.headItemRenderState = tempRenderState; + tempRenderState = temp; + } + } +} diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java index 97abd1f..f2a7409 100644 --- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java @@ -26,7 +26,7 @@ public class ReplaceItemModelPatch implements IntrospectableItemModelManager { private Function<Identifier, ItemModel> modelGetter; @WrapOperation( - method = "update(Lnet/minecraft/client/render/item/ItemRenderState;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;I)V", + method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;")) private Object replaceItemModelByIdentifier(ItemStack instance, ComponentType componentType, Operation<Object> original) { var override = CustomItemModelEvent.getModelIdentifier(instance, this); diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java index 850ea53..75cedf8 100644 --- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java +++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java @@ -5,6 +5,7 @@ import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.llamalad7.mixinextras.sugar.Local; import moe.nea.firmament.Firmament; import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures; +import moe.nea.firmament.features.texturepack.HeadModelChooser; import moe.nea.firmament.features.texturepack.PredicateModel; import moe.nea.firmament.util.ErrorUtil; import net.minecraft.client.item.ItemAsset; @@ -61,6 +62,7 @@ public class SupplyFakeModelPatch { try (var is = resource.getInputStream()) { var jsonObject = Firmament.INSTANCE.getGson().fromJson(new InputStreamReader(is), JsonObject.class); unbakedModel = PredicateModel.Unbaked.fromLegacyJson(jsonObject, unbakedModel); + unbakedModel = HeadModelChooser.Unbaked.fromLegacyJson(jsonObject, unbakedModel); } catch (Exception e) { ErrorUtil.INSTANCE.softError("Could not create resource for fake model supplication: " + model.getKey(), e); } diff --git a/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt index fe1518f..3eaf3d6 100644 --- a/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt +++ b/symbols/src/main/kotlin/process/SubscribeAnnotationProcessor.kt @@ -25,7 +25,7 @@ class SubscribeAnnotationProcessor( override fun finish() { subscriptions.sort() if (subscriptions.isEmpty()) return - val subscriptionSet = subscriptions.mapTo(mutableSetOf()) { it.parent.containingFile!! } + val subscriptionSet = subscriptions.mapTo(mutableSetOf()) { it.cf } val dependencies = Dependencies( aggregating = true, *subscriptionSet.toTypedArray()) @@ -37,9 +37,6 @@ class SubscribeAnnotationProcessor( subscriptionsFile.apply { appendLine("// This file is @generated by SubscribeAnnotationProcessor") appendLine("// Do not edit") - for (file in subscriptionSet) { - appendLine("// Dependency: ${file.filePath}") - } appendLine("package moe.nea.firmament.annotations.generated.$sourceSetName") appendLine() appendLine("import moe.nea.firmament.events.subscription.*") @@ -48,7 +45,7 @@ class SubscribeAnnotationProcessor( appendLine("class $generatedFileName : SubscriptionList {") appendLine(" override fun provideSubscriptions(addSubscription: (Subscription<*>) -> Unit) {") for (subscription in subscriptions) { - val owner = subscription.parent.qualifiedName!!.asString() + val owner = subscription.pQName.asString() val method = subscription.child.simpleName.asString() val type = subscription.type.declaration.qualifiedName!!.asString() appendLine(" addSubscription(Subscription<$type>(") @@ -75,13 +72,15 @@ class SubscribeAnnotationProcessor( val child: KSFunctionDeclaration, val type: KSType, ) : Comparable<Subscription> { + val cf = parent.containingFile!! + val pQName = parent.qualifiedName!! + val tName = type.declaration.qualifiedName!! override fun compareTo(other: Subscription): Int { - var compare = parent.qualifiedName!!.asString().compareTo(other.parent.qualifiedName!!.asString()) + var compare = pQName.asString().compareTo(other.pQName.asString()) if (compare != 0) return compare compare = other.child.simpleName.asString().compareTo(child.simpleName.asString()) if (compare != 0) return compare - compare = other.type.declaration.qualifiedName!!.asString() - .compareTo(type.declaration.qualifiedName!!.asString()) + compare = other.tName.asString().compareTo(tName.asString()) if (compare != 0) return compare return 0 } diff --git a/translations/en_us.json b/translations/en_us.json index 8033eaf..57e20a6 100644 --- a/translations/en_us.json +++ b/translations/en_us.json @@ -122,6 +122,8 @@ "firmament.config.fixes.disable-hurt-cam.description": "Disable the damage screen shake animation.", "firmament.config.fixes.hide-mob-effects": "Hide Potion Effects", "firmament.config.fixes.hide-mob-effects.description": "Hide Potion effects on the right side of your player inventory.", + "firmament.config.fixes.hide-slot-highlights": "Hide Slot Highlights", + "firmament.config.fixes.hide-slot-highlights.description": "Hide slot highlights for items with disabled tooltip. This makes /sbmenu look nicer with smooth texture packs.", "firmament.config.fixes.peek-chat": "Peek Chat", "firmament.config.fixes.peek-chat.description": "Hold this keybinding to view the chat as if you have it opened, but while still being able to control your character.", "firmament.config.fixes.player-skins": "Fix unsigned Player Skins", @@ -161,8 +163,14 @@ "firmament.config.pets": "Pets", "firmament.config.pets.highlight-pet": "Highlight active pet", "firmament.config.pets.highlight-pet.description": "Highlight your currently selected pet in the /pets menu.", + "firmament.config.pets.pet-overlay": "Pet Info", + "firmament.config.pets.pet-overlay-hud": "Pet Info Hud", + "firmament.config.pets.pet-overlay-hud.description": "A HUD showing current active pet and the pet exp.", + "firmament.config.pets.pet-overlay.description": "Shows current active pet and pet exp on screen.", "firmament.config.pickaxe-info": "Pickaxes & Drills", "firmament.config.pickaxe-info.ability-cooldown": "Pickaxe Ability Cooldown", + "firmament.config.pickaxe-info.ability-cooldown-toast": "Pickaxe Ability Ready Toast", + "firmament.config.pickaxe-info.ability-cooldown-toast.description": "Shows a toast when your pickaxe ability is ready.", "firmament.config.pickaxe-info.ability-cooldown.description": "Show a cooldown on your cross-hair for your pickaxe ability.", "firmament.config.pickaxe-info.ability-scale": "Ability Cooldown Scale", "firmament.config.pickaxe-info.ability-scale.description": "Resize the cooldown around your cross-hair for your pickaxe ability.", @@ -186,6 +194,8 @@ "firmament.config.power-user.copy-skull-texture.description": "Copy the texture location that can be used to re-texture the skull under your cross-hair.", "firmament.config.power-user.copy-texture-pack-id": "Copy Texture Pack Id", "firmament.config.power-user.copy-texture-pack-id.description": "Copy the texture pack id that is used for the item stack under your cursor.", + "firmament.config.power-user.copy-title": "Copy Inventory Title", + "firmament.config.power-user.copy-title.description": "Copies Inventory and Screen Titles", "firmament.config.power-user.entity-data": "Show Entity Data", "firmament.config.power-user.entity-data.description": "Print out information about the entity under your cross-hair.", "firmament.config.power-user.show-item-id": "Show SkyBlock Ids", @@ -257,6 +267,8 @@ "firmament.config.storage-overlay": "Storage Overlay", "firmament.config.storage-overlay.always-replace": "Always Open Overlay", "firmament.config.storage-overlay.always-replace.description": "Always replace the ender chest with Firmament's storage overlay.", + "firmament.config.storage-overlay.block-item-scrolling": "Block Scrolling on Items", + "firmament.config.storage-overlay.block-item-scrolling.description": "Disables scrolling the storage overlay screen while you are hovering over an item. Useful if you have a tooltip scrolling mod.", "firmament.config.storage-overlay.height": "Storage Height", "firmament.config.storage-overlay.height.description": "The height of the scrollable storage panel.", "firmament.config.storage-overlay.inverse-scroll": "Invert Scroll", @@ -299,7 +311,6 @@ "firmament.inventory-buttons.save-preset": "Save Preset", "firmament.key.category": "Firmament", "firmament.keybinding.external": "%s", - "firmament.mixins.start": "Applied firmament mixins:", "firmament.modapi.event": "Received mod API event: %s", "firmament.poweruser.entity.armor": "Entity Armor:", "firmament.poweruser.entity.armor.item": " - %s", diff --git a/web/src/pages/docs/_texture-pack-format.md b/web/src/pages/docs/_texture-pack-format.md index da66043..182c413 100644 --- a/web/src/pages/docs/_texture-pack-format.md +++ b/web/src/pages/docs/_texture-pack-format.md @@ -139,11 +139,31 @@ Filter by item type: "firmament:item": "minecraft:clock" ``` +#### Skulls + +You can match skulls using the skull textures and other properties using the skull predicate. If there are no properties specified this is equivalent to checking if the item is a `minecraft:player_head`. + +```json +"firmament:skull": { + "profileId": "cca2d452-c6d3-39cb-b695-5ec92b2d6729", + "textureProfileId": "1d5233d388624bafb00e3150a7aa3a89", + "skinUrl": "http://textures.minecraft.net/texture/7bf01c198f6e16965e230235cd22a5a9f4a40e40941234478948ff9a56e51775", + "textureValue": "ewogICJ0aW1lc3RhbXAiIDogMTYxODUyMTY2MzY1NCwKICAicHJvZmlsZUlkIiA6ICIxZDUyMzNkMzg4NjI0YmFmYjAwZTMxNTBhN2FhM2E4OSIsCiAgInByb2ZpbGVOYW1lIiA6ICIwMDAwMDAwMDAwMDAwMDBKIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzdiZjAxYzE5OGY2ZTE2OTY1ZTIzMDIzNWNkMjJhNWE5ZjRhNDBlNDA5NDEyMzQ0Nzg5NDhmZjlhNTZlNTE3NzUiLAogICAgICAibWV0YWRhdGEiIDogewogICAgICAgICJtb2RlbCIgOiAic2xpbSIKICAgICAgfQogICAgfQogIH0KfQ" +} +``` + +| Name | Type | Description | +|--------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `profileId` | UUID | Match the uuid of the profile component directly. | +| `textureProfileId` | UUID | Match the uuid of the skin owner in the encoded texture value. This is more expensive, but can deviate from the profile id of the profile owner. | +| `skinUrl` | [string](#string-matcher) | Match the texture url of the skin. This starts with `http://`, not with `https:/` in most cases. | +| `textureValue` | [string](#string-matcher) | Match the texture value. This is the encoded base64 string of the texture url along with metadata. It is faster to query than the `skinUrl`, but it can out of changed without causing any semantic changes, and is less readable than the skinUrl. | + #### Extra attributes Filter by extra attribute NBT data: -Specify a `path` to look at, separating sub elements with a `.`. You can use a `*` to check any child. +Specify a `path` (using an [nbt prism](#nbt-prism)) to look at, separating sub elements with a `.`. You can use a `*` to check any child. Then either specify a `match` sub-object or directly inline that object in the format of an [nbt matcher](#nbt-matcher). @@ -167,6 +187,32 @@ Sub object match: } ``` +#### Components + +You can match generic components similarly to [extra attributes](#extra-attributes). If you want to match an extra +attribute match directly using that, for better performance. + +You can specify a `path` (using an [nbt prism](#nbt-prism)) and match similar to extra attributes, but in addition you can also specify a `component`. This +variable is the identifier of a component type that will then be encoded to nbt and matched according to the `match` +using a [nbt matcher](#nbt-matcher). + +```json5 +"firmament:component": { + "path": "rgb", + "component": "minecraft:dyed_color", + "int": 255 +} +// Alternatively +"firmament:component": { + "path": "rgb", + "component": "minecraft:dyed_color", + "match": { + "int": 255 + } +} +``` + + #### Pet Data Filter by pet information. While you can already filter by the skyblock id for pet type and tier, this allows you to @@ -310,6 +356,58 @@ compare your number: This example would match if the level is less than fifty. The available operators are `<`, `>`, `<=` and `>=`. The operator needs to be specified on the left. The versions of the operator with `=` also allow the number to be equal. +### Nbt Prism + +An nbt prism (or path) is used to specify where in a complex nbt construct to look for a value. A basic prism just looks +like a dot-separated path (`parent.child.grandchild`), but more complex paths can be constructed. + +First the specified path is split into dot separated chunks: `"a.b.c"` -> `["a", "b", "c"]`. You can also directly +specify the list if you would like. Any entry in that list not starting with a `*` is treated as an attribute name or +an index: + +```json +{ + "propA": { + "propB": { + "propC": 100, + "propD": 1000 + } + }, + "someOtherProp": "hello", + "someThirdProp": "{\"innerProp\": true}", + "someFourthProp": "aGlkZGVuIHZhbHVl" +} +``` + +In this example json (which is supposed to represent a corresponding nbt object), you can use a path like +`propA.propB.propC` to directly extract the value `100`. + +If you want to extract all of the innermost values of `propB` +(for example if `propB` was an array instead), you could use `propA.propB.*`. You can use the `*` at any level: +`*.*.*` for example extracts all properties that are exactly at the third level. In that case you would try to match any +of the values of `[100, 1000]` to your match object. + +Sometimes values are encoded in a non-nbt format inside a string. For those you can use other star based directives like +`*base64` or `*json` to decode those entries. + +`*base64` turns a base64 encoded string into the base64 decoded counterpart. `*json` decodes a string into the json +object represented by that string. Note that json to nbt conversion isn't always straightforwards and the types can +end up being mangled (for example what could have been a byte ends up an int). + +| Path | Result | +|---------------------------------|---------------------------------| +| `propA.propB` | `{"propC": 100, "propD": 1000}` | +| `propA.propB.propC` | `100` | +| `propA.*.propC` | `100` | +| `propA.propB.*` | `100`, `1000` | +| `someOtherProp` | `"hello"` | +| `someThirdProp` | "{\"innerProp\": true}" | +| `someThirdProp.*json` | {"innerProp": true} | +| `someThirdProp.*json.innerProp` | true | +| `someFourthProp` | `"aGlkZGVuIHZhbHVl"` | +| `someFourthProp.*base64` | `"hidden value"` | + + ### Nbt Matcher This matches a single nbt element. |