Make pickaxe ability display use AbilityUtils

[no changelog]
This commit is contained in:
Linnea Gräf
2024-10-13 17:32:10 +02:00
parent daa63bd914
commit e6142bb936
24 changed files with 1433 additions and 681 deletions

View File

@@ -41,3 +41,9 @@ SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"
path = ["**/META-INF/services/*"] path = ["**/META-INF/services/*"]
SPDX-License-Identifier = "CC0-1.0" SPDX-License-Identifier = "CC0-1.0"
SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"] SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"]
[[annotations]]
path = ["src/test/resources/testdata/**/*.snbt"]
SPDX-License-Identifier = "CC-BY-4.0"
SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"]

View File

@@ -7,6 +7,7 @@
*/ */
import com.google.devtools.ksp.gradle.KspTaskJvm import com.google.devtools.ksp.gradle.KspTaskJvm
import com.google.gson.JsonArray
import moe.nea.licenseextractificator.LicenseDiscoveryTask import moe.nea.licenseextractificator.LicenseDiscoveryTask
import net.fabricmc.loom.LoomGradleExtension import net.fabricmc.loom.LoomGradleExtension
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
@@ -14,353 +15,356 @@ import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
java java
`maven-publish` `maven-publish`
alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.plugin.serialization) alias(libs.plugins.kotlin.plugin.serialization)
alias(libs.plugins.kotlin.plugin.powerassert) alias(libs.plugins.kotlin.plugin.powerassert)
alias(libs.plugins.kotlin.plugin.ksp) alias(libs.plugins.kotlin.plugin.ksp)
alias(libs.plugins.loom) alias(libs.plugins.loom)
id("com.github.johnrengelman.shadow") version "8.1.1" id("com.github.johnrengelman.shadow") version "8.1.1"
id("moe.nea.licenseextractificator") id("moe.nea.licenseextractificator")
} }
version = getGitTagInfo() version = getGitTagInfo()
group = rootProject.property("maven_group").toString() group = rootProject.property("maven_group").toString()
java { java {
withSourcesJar() withSourcesJar()
toolchain { toolchain {
languageVersion.set(JavaLanguageVersion.of(21)) languageVersion.set(JavaLanguageVersion.of(21))
} }
} }
tasks.withType(KotlinCompile::class) { tasks.withType(KotlinCompile::class) {
compilerOptions { compilerOptions {
jvmTarget.set(JvmTarget.JVM_21) jvmTarget.set(JvmTarget.JVM_21)
} }
} }
allprojects { allprojects {
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://maven.terraformersmc.com/releases/") maven("https://maven.terraformersmc.com/releases/")
maven("https://maven.shedaniel.me") maven("https://maven.shedaniel.me")
maven("https://maven.fabricmc.net") maven("https://maven.fabricmc.net")
maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1")
maven("https://api.modrinth.com/maven") { maven("https://api.modrinth.com/maven") {
content { content {
includeGroup("maven.modrinth") includeGroup("maven.modrinth")
} }
} }
maven("https://repo.sleeping.town") { maven("https://repo.sleeping.town") {
content { content {
includeGroup("com.unascribed") includeGroup("com.unascribed")
} }
} }
ivy("https://github.com/HotswapProjects/HotswapAgent/releases/download") { ivy("https://github.com/HotswapProjects/HotswapAgent/releases/download") {
patternLayout { patternLayout {
artifact("[revision]/[artifact]-[revision].[ext]") artifact("[revision]/[artifact]-[revision].[ext]")
} }
content { content {
includeGroup("virtual.github.hotswapagent") includeGroup("virtual.github.hotswapagent")
} }
metadataSources { metadataSources {
artifact() artifact()
} }
} }
maven("https://server.bbkr.space/artifactory/libs-release") maven("https://server.bbkr.space/artifactory/libs-release")
maven("https://repo.nea.moe/releases") maven("https://repo.nea.moe/releases")
maven("https://maven.notenoughupdates.org/releases") maven("https://maven.notenoughupdates.org/releases")
maven("https://repo.nea.moe/mirror") maven("https://repo.nea.moe/mirror")
maven("https://jitpack.io/") { maven("https://jitpack.io/") {
content { content {
includeGroupByRegex("(com|io)\\.github\\..+") includeGroupByRegex("(com|io)\\.github\\..+")
excludeModule("io.github.cottonmc", "LibGui") excludeModule("io.github.cottonmc", "LibGui")
} }
} }
maven("https://repo.hypixel.net/repository/Hypixel/") maven("https://repo.hypixel.net/repository/Hypixel/")
maven("https://maven.azureaaron.net/snapshots") maven("https://maven.azureaaron.net/snapshots")
maven("https://maven.azureaaron.net/releases") maven("https://maven.azureaaron.net/releases")
maven("https://www.cursemaven.com") maven("https://www.cursemaven.com")
mavenLocal() mavenLocal()
} }
} }
kotlin { kotlin {
sourceSets.all { sourceSets.all {
languageSettings { languageSettings {
enableLanguageFeature("BreakContinueInInlineLambdas") enableLanguageFeature("BreakContinueInInlineLambdas")
} }
} }
} }
fun String.capitalizeN() = replaceFirstChar { it.uppercaseChar() } fun String.capitalizeN() = replaceFirstChar { it.uppercaseChar() }
fun innerJarsOf(name: String, dependency: Dependency): FileCollection { fun innerJarsOf(name: String, dependency: Dependency): FileCollection {
val task = tasks.create("unpackInnerJarsFor${name.capitalizeN()}", InnerJarsUnpacker::class) { val task = tasks.create("unpackInnerJarsFor${name.capitalizeN()}", InnerJarsUnpacker::class) {
this.inputJars.setFrom(files(configurations.detachedConfiguration(dependency))) this.inputJars.setFrom(files(configurations.detachedConfiguration(dependency)))
this.outputDir.set(layout.buildDirectory.dir("unpackedJars/$name").also { this.outputDir.set(layout.buildDirectory.dir("unpackedJars/$name").also {
it.get().asFile.mkdirs() it.get().asFile.mkdirs()
}) })
} }
println("Constructed innerJars task: ${project.files(task).toList()}") println("Constructed innerJars task: ${project.files(task).toList()}")
return project.files(task) return project.files(task)
} }
val compatSourceSets: MutableSet<SourceSet> = mutableSetOf() val compatSourceSets: MutableSet<SourceSet> = mutableSetOf()
fun createIsolatedSourceSet(name: String, path: String = "compat/$name"): SourceSet { fun createIsolatedSourceSet(name: String, path: String = "compat/$name"): SourceSet {
val ss = sourceSets.create(name) { val ss = sourceSets.create(name) {
this.java.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java"))) this.java.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
this.kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java"))) this.kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
} }
compatSourceSets.add(ss) compatSourceSets.add(ss)
loom.createRemapConfigurations(ss) loom.createRemapConfigurations(ss)
val mainSS = sourceSets.main.get() val mainSS = sourceSets.main.get()
val upperName = ss.name.capitalizeN() val upperName = ss.name.capitalizeN()
configurations { configurations {
(ss.implementationConfigurationName) { (ss.implementationConfigurationName) {
extendsFrom(getByName(mainSS.compileClasspathConfigurationName)) extendsFrom(getByName(mainSS.compileClasspathConfigurationName))
} }
(ss.annotationProcessorConfigurationName) { (ss.annotationProcessorConfigurationName) {
extendsFrom(getByName(mainSS.annotationProcessorConfigurationName)) extendsFrom(getByName(mainSS.annotationProcessorConfigurationName))
} }
(mainSS.runtimeOnlyConfigurationName) { (mainSS.runtimeOnlyConfigurationName) {
extendsFrom(getByName(ss.runtimeClasspathConfigurationName)) extendsFrom(getByName(ss.runtimeClasspathConfigurationName))
} }
("ksp$upperName") { ("ksp$upperName") {
extendsFrom(ksp.get()) extendsFrom(ksp.get())
} }
} }
afterEvaluate { afterEvaluate {
tasks.named("ksp${upperName}Kotlin", KspTaskJvm::class) { tasks.named("ksp${upperName}Kotlin", KspTaskJvm::class) {
this.options.add(SubpluginOption("apoption", "firmament.sourceset=${ss.name}")) this.options.add(SubpluginOption("apoption", "firmament.sourceset=${ss.name}"))
} }
} }
dependencies { dependencies {
runtimeOnly(ss.output) runtimeOnly(ss.output)
(ss.implementationConfigurationName)(sourceSets.main.get().output) (ss.implementationConfigurationName)(sourceSets.main.get().output)
} }
tasks.shadowJar { tasks.shadowJar {
from(ss.output) from(ss.output)
} }
return ss return ss
} }
val SourceSet.modImplementationConfigurationName val SourceSet.modImplementationConfigurationName
get() = get() =
loom.remapConfigurations.find { loom.remapConfigurations.find {
it.targetConfigurationName.get() == this.implementationConfigurationName it.targetConfigurationName.get() == this.implementationConfigurationName
}!!.sourceConfiguration }!!.sourceConfiguration
val configuredSourceSet = createIsolatedSourceSet("configured") val configuredSourceSet = createIsolatedSourceSet("configured")
val sodiumSourceSet = createIsolatedSourceSet("sodium") val sodiumSourceSet = createIsolatedSourceSet("sodium")
val citResewnSourceSet = createIsolatedSourceSet("citresewn") val citResewnSourceSet = createIsolatedSourceSet("citresewn")
val shadowMe by configurations.creating { val shadowMe by configurations.creating {
exclude(group = "org.jetbrains.kotlin") exclude(group = "org.jetbrains.kotlin")
exclude(group = "org.jetbrains.kotlinx") exclude(group = "org.jetbrains.kotlinx")
exclude(group = "org.jetbrains") exclude(group = "org.jetbrains")
exclude(module = "gson") exclude(module = "gson")
exclude(group = "org.slf4j") exclude(group = "org.slf4j")
} }
val transInclude by configurations.creating { val transInclude by configurations.creating {
exclude(group = "com.mojang") exclude(group = "com.mojang")
exclude(group = "org.jetbrains.kotlin") exclude(group = "org.jetbrains.kotlin")
exclude(group = "org.jetbrains.kotlinx") exclude(group = "org.jetbrains.kotlinx")
isTransitive = true isTransitive = true
} }
val hotswap by configurations.creating { val hotswap by configurations.creating {
isVisible = false isVisible = false
} }
val nonModImplentation by configurations.creating { val nonModImplentation by configurations.creating {
configurations.implementation.get().extendsFrom(this) configurations.implementation.get().extendsFrom(this)
} }
dependencies { dependencies {
// Minecraft dependencies // Minecraft dependencies
"minecraft"(libs.minecraft) "minecraft"(libs.minecraft)
"mappings"("net.fabricmc:yarn:${libs.versions.yarn.get()}:v2") "mappings"("net.fabricmc:yarn:${libs.versions.yarn.get()}:v2")
// Hotswap Dependency // Hotswap Dependency
hotswap(libs.hotswap) hotswap(libs.hotswap)
// Fabric dependencies // Fabric dependencies
modImplementation(libs.fabric.loader) modImplementation(libs.fabric.loader)
modImplementation(libs.fabric.kotlin) modImplementation(libs.fabric.kotlin)
modImplementation(libs.modmenu) modImplementation(libs.modmenu)
modImplementation(libs.moulconfig) modImplementation(libs.moulconfig)
modImplementation(libs.manninghamMills) modImplementation(libs.manninghamMills)
modCompileOnly(libs.explosiveenhancement) modCompileOnly(libs.explosiveenhancement)
modImplementation(libs.hypixelmodapi) modImplementation(libs.hypixelmodapi)
include(libs.hypixelmodapi.fabric) include(libs.hypixelmodapi.fabric)
compileOnly(project(":javaplugin")) compileOnly(project(":javaplugin"))
annotationProcessor(project(":javaplugin")) annotationProcessor(project(":javaplugin"))
implementation("com.google.auto.service:auto-service-annotations:1.1.1") implementation("com.google.auto.service:auto-service-annotations:1.1.1")
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0") ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
include(libs.manninghamMills) include(libs.manninghamMills)
include(libs.moulconfig) include(libs.moulconfig)
annotationProcessor(libs.mixinextras) annotationProcessor(libs.mixinextras)
implementation(libs.mixinextras) implementation(libs.mixinextras)
include(libs.mixinextras) include(libs.mixinextras)
nonModImplentation(libs.nealisp) nonModImplentation(libs.nealisp)
shadowMe(libs.nealisp) shadowMe(libs.nealisp)
modCompileOnly(libs.fabric.api) modCompileOnly(libs.fabric.api)
modRuntimeOnly(libs.fabric.api.deprecated) modRuntimeOnly(libs.fabric.api.deprecated)
modApi(libs.architectury) modApi(libs.architectury)
modCompileOnly(libs.jarvis.api) modCompileOnly(libs.jarvis.api)
include(libs.jarvis.fabric) include(libs.jarvis.fabric)
modCompileOnly(libs.femalegender) modCompileOnly(libs.femalegender)
(configuredSourceSet.modImplementationConfigurationName)(libs.configured) (configuredSourceSet.modImplementationConfigurationName)(libs.configured)
(sodiumSourceSet.modImplementationConfigurationName)(libs.sodium) (sodiumSourceSet.modImplementationConfigurationName)(libs.sodium)
(citResewnSourceSet.modImplementationConfigurationName)( (citResewnSourceSet.modImplementationConfigurationName)(
innerJarsOf("citresewn", dependencies.create(libs.citresewn.get())).asFileTree) innerJarsOf("citresewn", dependencies.create(libs.citresewn.get())).asFileTree)
(citResewnSourceSet.modImplementationConfigurationName)(libs.citresewn) (citResewnSourceSet.modImplementationConfigurationName)(libs.citresewn)
// Actual dependencies // Actual dependencies
modCompileOnly(libs.rei.api) { modCompileOnly(libs.rei.api) {
exclude(module = "architectury") exclude(module = "architectury")
exclude(module = "architectury-fabric") exclude(module = "architectury-fabric")
} }
nonModImplentation(libs.repoparser) nonModImplentation(libs.repoparser)
shadowMe(libs.repoparser) shadowMe(libs.repoparser)
fun ktor(mod: String) = "io.ktor:ktor-$mod-jvm:${libs.versions.ktor.get()}" fun ktor(mod: String) = "io.ktor:ktor-$mod-jvm:${libs.versions.ktor.get()}"
modCompileOnly(libs.citresewn) modCompileOnly(libs.citresewn)
transInclude(nonModImplentation(ktor("client-core"))!!) transInclude(nonModImplentation(ktor("client-core"))!!)
transInclude(nonModImplentation(ktor("client-java"))!!) transInclude(nonModImplentation(ktor("client-java"))!!)
transInclude(nonModImplentation(ktor("serialization-kotlinx-json"))!!) transInclude(nonModImplentation(ktor("serialization-kotlinx-json"))!!)
transInclude(nonModImplentation(ktor("client-content-negotiation"))!!) transInclude(nonModImplentation(ktor("client-content-negotiation"))!!)
transInclude(nonModImplentation(ktor("client-encoding"))!!) transInclude(nonModImplentation(ktor("client-encoding"))!!)
transInclude(nonModImplentation(ktor("client-logging"))!!) transInclude(nonModImplentation(ktor("client-logging"))!!)
// Dev environment preinstalled mods // Dev environment preinstalled mods
modLocalRuntime(libs.bundles.runtime.required) modLocalRuntime(libs.bundles.runtime.required)
modLocalRuntime(libs.bundles.runtime.optional) modLocalRuntime(libs.bundles.runtime.optional)
modLocalRuntime(libs.jarvis.fabric) modLocalRuntime(libs.jarvis.fabric)
transInclude.resolvedConfiguration.resolvedArtifacts.forEach { transInclude.resolvedConfiguration.resolvedArtifacts.forEach {
include(it.moduleVersion.id.toString()) include(it.moduleVersion.id.toString())
} }
testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") testImplementation("org.junit.jupiter:junit-jupiter:5.9.2")
implementation(project(":symbols")) implementation(project(":symbols"))
ksp(project(":symbols")) ksp(project(":symbols"))
}
tasks.test {
useJUnitPlatform()
} }
loom { loom {
clientOnlyMinecraftJar() clientOnlyMinecraftJar()
accessWidenerPath.set(project.file("src/main/resources/firmament.accesswidener")) accessWidenerPath.set(project.file("src/main/resources/firmament.accesswidener"))
runs { runs {
removeIf { it.name != "client" } removeIf { it.name != "client" }
named("client") { configureEach {
property("devauth.enabled", "true") property("fabric.log.level", "info")
property("fabric.log.level", "info") property("firmament.debug", "true")
property("firmament.debug", "true") property("firmament.classroots",
property("firmament.classroots", compatSourceSets.joinToString(File.pathSeparator) {
compatSourceSets.joinToString(File.pathSeparator) { File(it.output.classesDirs.asPath).absolutePath
File(it.output.classesDirs.asPath).absolutePath })
}) property("mixin.debug", "true")
property("mixin.debug", "true")
parseEnvFile(file(".env")).forEach { (t, u) -> parseEnvFile(file(".env")).forEach { (t, u) ->
environmentVariable(t, u) environmentVariable(t, u)
} }
parseEnvFile(file(".properties")).forEach { (t, u) -> parseEnvFile(file(".properties")).forEach { (t, u) ->
property(t, u) property(t, u)
} }
vmArg("-ea") }
vmArg("-XX:+AllowEnhancedClassRedefinition") named("client") {
vmArg("-XX:HotswapAgent=external") property("devauth.enabled", "true")
vmArg("-javaagent:${hotswap.resolve().single().absolutePath}") vmArg("-ea")
} vmArg("-XX:+AllowEnhancedClassRedefinition")
} vmArg("-XX:HotswapAgent=external")
vmArg("-javaagent:${hotswap.resolve().single().absolutePath}")
}
}
} }
tasks.test {
useJUnitPlatform()
}
tasks.withType<JavaCompile> { tasks.withType<JavaCompile> {
this.sourceCompatibility = "21" this.sourceCompatibility = "21"
this.targetCompatibility = "21" this.targetCompatibility = "21"
options.encoding = "UTF-8" options.encoding = "UTF-8"
val module = "ALL-UNNAMED" val module = "ALL-UNNAMED"
options.forkOptions.jvmArgs!!.addAll(listOf( options.forkOptions.jvmArgs!!.addAll(listOf(
"--add-exports=jdk.compiler/com.sun.tools.javac.util=$module", "--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.comp=$module",
"--add-exports=jdk.compiler/com.sun.tools.javac.tree=$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.api=$module",
"--add-exports=jdk.compiler/com.sun.tools.javac.code=$module", "--add-exports=jdk.compiler/com.sun.tools.javac.code=$module",
)) ))
options.isFork = true options.isFork = true
afterEvaluate { afterEvaluate {
options.compilerArgs.add("-Xplugin:IntermediaryNameReplacement mappingFile=${LoomGradleExtension.get(project).mappingsFile.absolutePath} sourceNs=named") options.compilerArgs.add("-Xplugin:IntermediaryNameReplacement mappingFile=${LoomGradleExtension.get(project).mappingsFile.absolutePath} sourceNs=named")
} }
} }
tasks.jar { tasks.jar {
destinationDirectory.set(layout.buildDirectory.dir("badjars")) destinationDirectory.set(layout.buildDirectory.dir("badjars"))
archiveClassifier.set("slim") archiveClassifier.set("slim")
} }
tasks.shadowJar { tasks.shadowJar {
configurations = listOf(shadowMe) configurations = listOf(shadowMe)
archiveClassifier.set("dev") archiveClassifier.set("dev")
relocate("io.github.moulberry.repo", "moe.nea.firmament.deps.repo") relocate("io.github.moulberry.repo", "moe.nea.firmament.deps.repo")
destinationDirectory.set(layout.buildDirectory.dir("badjars")) destinationDirectory.set(layout.buildDirectory.dir("badjars"))
mergeServiceFiles() mergeServiceFiles()
} }
tasks.remapJar { tasks.remapJar {
injectAccessWidener.set(true) injectAccessWidener.set(true)
inputFile.set(tasks.shadowJar.flatMap { it.archiveFile }) inputFile.set(tasks.shadowJar.flatMap { it.archiveFile })
dependsOn(tasks.shadowJar) dependsOn(tasks.shadowJar)
archiveClassifier.set("") archiveClassifier.set("")
} }
tasks.processResources { tasks.processResources {
val replacements = listOf( val replacements = listOf(
"version" to project.version.toString(), "version" to project.version.toString(),
"minecraft_version" to libs.versions.minecraft.get(), "minecraft_version" to libs.versions.minecraft.get(),
"fabric_kotlin_version" to libs.versions.fabric.kotlin.get(), "fabric_kotlin_version" to libs.versions.fabric.kotlin.get(),
"rei_version" to libs.versions.rei.get() "rei_version" to libs.versions.rei.get()
) )
replacements.forEach { (key, value) -> inputs.property(key, value) } replacements.forEach { (key, value) -> inputs.property(key, value) }
filesMatching("**/fabric.mod.json") { filesMatching("**/fabric.mod.json") {
expand(*replacements.toTypedArray()) expand(*replacements.toTypedArray())
} }
exclude("**/*.license") exclude("**/*.license")
from(tasks.scanLicenses) from(tasks.scanLicenses)
} }
tasks.scanLicenses { tasks.scanLicenses {
scanConfiguration(nonModImplentation) scanConfiguration(nonModImplentation)
scanConfiguration(configurations.modCompileClasspath.get()) scanConfiguration(configurations.modCompileClasspath.get())
outputFile.set(layout.buildDirectory.file("LICENSES-FIRMAMENT.json")) outputFile.set(layout.buildDirectory.file("LICENSES-FIRMAMENT.json"))
licenseFormatter.set(moe.nea.licenseextractificator.JsonLicenseFormatter()) licenseFormatter.set(moe.nea.licenseextractificator.JsonLicenseFormatter())
} }
tasks.create("printAllLicenses", LicenseDiscoveryTask::class.java, licensing).apply { tasks.create("printAllLicenses", LicenseDiscoveryTask::class.java, licensing).apply {
outputFile.set(layout.buildDirectory.file("LICENSES-FIRMAMENT.txt")) outputFile.set(layout.buildDirectory.file("LICENSES-FIRMAMENT.txt"))
licenseFormatter.set(moe.nea.licenseextractificator.TextLicenseFormatter()) licenseFormatter.set(moe.nea.licenseextractificator.TextLicenseFormatter())
scanConfiguration(nonModImplentation) scanConfiguration(nonModImplentation)
scanConfiguration(configurations.modCompileClasspath.get()) scanConfiguration(configurations.modCompileClasspath.get())
doLast { doLast {
println(outputFile.get().asFile.readText()) println(outputFile.get().asFile.readText())
} }
outputs.upToDateWhen { false } outputs.upToDateWhen { false }
} }
tasks.withType<AbstractArchiveTask>().configureEach { tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false isPreserveFileTimestamps = false
isReproducibleFileOrder = true isReproducibleFileOrder = true
} }
licensing.addExtraLicenseMatchers() licensing.addExtraLicenseMatchers()

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament package moe.nea.firmament
import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.CommandDispatcher
@@ -33,7 +31,6 @@ import kotlinx.coroutines.plus
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import net.minecraft.client.render.chunk.SectionBuilder
import net.minecraft.command.CommandRegistryAccess import net.minecraft.command.CommandRegistryAccess
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import moe.nea.firmament.commands.registerFirmamentCommand import moe.nea.firmament.commands.registerFirmamentCommand
@@ -51,98 +48,98 @@ import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.data.IDataHolder import moe.nea.firmament.util.data.IDataHolder
object Firmament { object Firmament {
const val MOD_ID = "firmament" const val MOD_ID = "firmament"
val DEBUG = System.getProperty("firmament.debug") == "true" val DEBUG = System.getProperty("firmament.debug") == "true"
val DATA_DIR: Path = Path.of(".firmament").also { Files.createDirectories(it) } val DATA_DIR: Path = Path.of(".firmament").also { Files.createDirectories(it) }
val CONFIG_DIR: Path = Path.of("config/firmament").also { Files.createDirectories(it) } val CONFIG_DIR: Path = Path.of("config/firmament").also { Files.createDirectories(it) }
val logger: Logger = LogManager.getLogger("Firmament") val logger: Logger = LogManager.getLogger("Firmament")
private val metadata: ModMetadata by lazy { private val metadata: ModMetadata by lazy {
FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata FabricLoader.getInstance().getModContainer(MOD_ID).orElseThrow().metadata
} }
val version: Version by lazy { metadata.version } val version: Version by lazy { metadata.version }
val json = Json { val json = Json {
prettyPrint = DEBUG prettyPrint = DEBUG
isLenient = true isLenient = true
ignoreUnknownKeys = true ignoreUnknownKeys = true
encodeDefaults = true encodeDefaults = true
} }
val httpClient by lazy { val httpClient by lazy {
HttpClient { HttpClient {
install(ContentNegotiation) { install(ContentNegotiation) {
json(json) json(json)
} }
install(ContentEncoding) { install(ContentEncoding) {
gzip() gzip()
deflate() deflate()
} }
install(UserAgent) { install(UserAgent) {
agent = "Firmament/$version" agent = "Firmament/$version"
} }
if (DEBUG) if (DEBUG)
install(Logging) { install(Logging) {
level = LogLevel.INFO level = LogLevel.INFO
} }
install(HttpCache) install(HttpCache)
} }
} }
val globalJob = Job() val globalJob = Job()
val coroutineScope = val coroutineScope =
CoroutineScope(EmptyCoroutineContext + CoroutineName("Firmament")) + SupervisorJob(globalJob) CoroutineScope(EmptyCoroutineContext + CoroutineName("Firmament")) + SupervisorJob(globalJob)
private fun registerCommands( private fun registerCommands(
dispatcher: CommandDispatcher<FabricClientCommandSource>, dispatcher: CommandDispatcher<FabricClientCommandSource>,
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
ctx: CommandRegistryAccess ctx: CommandRegistryAccess
) { ) {
registerFirmamentCommand(dispatcher) registerFirmamentCommand(dispatcher)
CommandEvent.publish(CommandEvent(dispatcher, ctx, MC.networkHandler?.commandDispatcher)) CommandEvent.publish(CommandEvent(dispatcher, ctx, MC.networkHandler?.commandDispatcher))
} }
@JvmStatic @JvmStatic
fun onInitialize() { fun onInitialize() {
} }
@JvmStatic @JvmStatic
fun onClientInitialize() { fun onClientInitialize() {
FeatureManager.subscribeEvents() FeatureManager.subscribeEvents()
var tick = 0 var tick = 0
ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance -> ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance ->
TickEvent.publish(TickEvent(tick++)) TickEvent.publish(TickEvent(tick++))
}) })
IDataHolder.registerEvents() IDataHolder.registerEvents()
RepoManager.initialize() RepoManager.initialize()
SBData.init() SBData.init()
FeatureManager.autoload() FeatureManager.autoload()
HypixelStaticData.spawnDataCollectionLoop() HypixelStaticData.spawnDataCollectionLoop()
ClientCommandRegistrationCallback.EVENT.register(this::registerCommands) ClientCommandRegistrationCallback.EVENT.register(this::registerCommands)
ClientLifecycleEvents.CLIENT_STARTED.register(ClientLifecycleEvents.ClientStarted { ClientLifecycleEvents.CLIENT_STARTED.register(ClientLifecycleEvents.ClientStarted {
ClientStartedEvent.publish(ClientStartedEvent()) ClientStartedEvent.publish(ClientStartedEvent())
}) })
ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping { ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {
logger.info("Shutting down Firmament coroutines") logger.info("Shutting down Firmament coroutines")
globalJob.cancel() globalJob.cancel()
}) })
registerFirmamentEvents() registerFirmamentEvents()
ItemTooltipCallback.EVENT.register { stack, context, type, lines -> ItemTooltipCallback.EVENT.register { stack, context, type, lines ->
ItemTooltipEvent.publish(ItemTooltipEvent(stack, context, type, lines)) ItemTooltipEvent.publish(ItemTooltipEvent(stack, context, type, lines))
} }
ScreenEvents.AFTER_INIT.register(ScreenEvents.AfterInit { client, screen, scaledWidth, scaledHeight -> ScreenEvents.AFTER_INIT.register(ScreenEvents.AfterInit { client, screen, scaledWidth, scaledHeight ->
ScreenEvents.afterRender(screen) ScreenEvents.afterRender(screen)
.register(ScreenEvents.AfterRender { screen, drawContext, mouseX, mouseY, tickDelta -> .register(ScreenEvents.AfterRender { screen, drawContext, mouseX, mouseY, tickDelta ->
ScreenRenderPostEvent.publish(ScreenRenderPostEvent(screen, mouseX, mouseY, tickDelta, drawContext)) ScreenRenderPostEvent.publish(ScreenRenderPostEvent(screen, mouseX, mouseY, tickDelta, drawContext))
}) })
}) })
} }
fun identifier(path: String) = Identifier.of(MOD_ID, path) fun identifier(path: String) = Identifier.of(MOD_ID, path)
inline fun <reified T : Any> tryDecodeJsonFromStream(inputStream: InputStream): Result<T> { inline fun <reified T : Any> tryDecodeJsonFromStream(inputStream: InputStream): Result<T> {
return runCatching { return runCatching {
json.decodeFromStream<T>(inputStream) json.decodeFromStream<T>(inputStream)
} }
} }
} }

View File

@@ -4,10 +4,14 @@ package moe.nea.firmament.events
import net.minecraft.client.gui.DrawContext import net.minecraft.client.gui.DrawContext
import net.minecraft.client.render.RenderTickCounter import net.minecraft.client.render.RenderTickCounter
import net.minecraft.world.GameMode
import moe.nea.firmament.util.MC
/** /**
* Called when hud elements should be rendered, before the screen, but after the world. * Called when hud elements should be rendered, before the screen, but after the world.
*/ */
data class HudRenderEvent(val context: DrawContext, val tickDelta: RenderTickCounter) : FirmamentEvent() { data class HudRenderEvent(val context: DrawContext, val tickDelta: RenderTickCounter) : FirmamentEvent() {
val isRenderingHud = !MC.options.hudHidden
val isRenderingCursor = MC.interactionManager?.currentGameMode != GameMode.SPECTATOR && isRenderingHud
companion object : FirmamentEventBus<HudRenderEvent>() companion object : FirmamentEventBus<HudRenderEvent>()
} }

View File

@@ -28,6 +28,7 @@ import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
import moe.nea.firmament.util.ClipboardUtils import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.focusedItemStack import moe.nea.firmament.util.focusedItemStack
import moe.nea.firmament.util.mc.SNbtFormatter.Companion.toPrettyString
import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.skyBlockId
@@ -44,6 +45,7 @@ object PowerUserTools : FirmamentFeature {
val copyLoreData by keyBindingWithDefaultUnbound("copy-lore") val copyLoreData by keyBindingWithDefaultUnbound("copy-lore")
val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture") val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture")
val copyEntityData by keyBindingWithDefaultUnbound("entity-data") val copyEntityData by keyBindingWithDefaultUnbound("entity-data")
val copyItemStack by keyBindingWithDefaultUnbound("copy-item-stack")
} }
override val config override val config
@@ -125,7 +127,7 @@ object PowerUserTools : FirmamentFeature {
Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.modelid", model.toString())) Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.modelid", model.toString()))
} else if (it.matches(TConfig.copyNbtData)) { } else if (it.matches(TConfig.copyNbtData)) {
// TODO: copy full nbt // TODO: copy full nbt
val nbt = item.get(DataComponentTypes.CUSTOM_DATA)?.nbt?.toString() ?: "<empty>" val nbt = item.get(DataComponentTypes.CUSTOM_DATA)?.nbt?.toPrettyString() ?: "<empty>"
ClipboardUtils.setTextContent(nbt) ClipboardUtils.setTextContent(nbt)
lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.nbt")) lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.nbt"))
} else if (it.matches(TConfig.copyLoreData)) { } else if (it.matches(TConfig.copyLoreData)) {
@@ -157,6 +159,9 @@ object PowerUserTools : FirmamentFeature {
Text.stringifiedTranslatable("firmament.tooltip.copied.skull-id", skullTexture.toString()) Text.stringifiedTranslatable("firmament.tooltip.copied.skull-id", skullTexture.toString())
) )
println("Copied skull id: $skullTexture") println("Copied skull id: $skullTexture")
} else if (it.matches(TConfig.copyItemStack)) {
ClipboardUtils.setTextContent(item.encode(MC.currentOrDefaultRegistries).toPrettyString())
lastCopiedStack = Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.stack"))
} }
} }

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.features.mining package moe.nea.firmament.features.mining
import java.util.regex.Pattern import java.util.regex.Pattern
@@ -30,155 +29,146 @@ import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.parseTimePattern import moe.nea.firmament.util.parseTimePattern
import moe.nea.firmament.util.render.RenderCircleProgress import moe.nea.firmament.util.render.RenderCircleProgress
import moe.nea.firmament.util.render.lerp import moe.nea.firmament.util.render.lerp
import moe.nea.firmament.util.skyblock.AbilityUtils
import moe.nea.firmament.util.toShedaniel import moe.nea.firmament.util.toShedaniel
import moe.nea.firmament.util.unformattedString import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch import moe.nea.firmament.util.useMatch
object PickaxeAbility : FirmamentFeature { object PickaxeAbility : FirmamentFeature {
override val identifier: String override val identifier: String
get() = "pickaxe-info" get() = "pickaxe-info"
object TConfig : ManagedConfig(identifier) { object TConfig : ManagedConfig(identifier) {
val cooldownEnabled by toggle("ability-cooldown") { true } val cooldownEnabled by toggle("ability-cooldown") { true }
val cooldownScale by integer("ability-scale", 16, 64) { 16 } val cooldownScale by integer("ability-scale", 16, 64) { 16 }
val drillFuelBar by toggle("fuel-bar") { true } val drillFuelBar by toggle("fuel-bar") { true }
} }
var lobbyJoinTime = TimeMark.farPast() var lobbyJoinTime = TimeMark.farPast()
var lastUsage = mutableMapOf<String, TimeMark>() var lastUsage = mutableMapOf<String, TimeMark>()
var abilityOverride: String? = null var abilityOverride: String? = null
var defaultAbilityDurations = mutableMapOf<String, Duration>( var defaultAbilityDurations = mutableMapOf<String, Duration>(
"Mining Speed Boost" to 120.seconds, "Mining Speed Boost" to 120.seconds,
"Pickobulus" to 110.seconds, "Pickobulus" to 110.seconds,
"Gemstone Infusion" to 140.seconds, "Gemstone Infusion" to 140.seconds,
"Hazardous Miner" to 140.seconds, "Hazardous Miner" to 140.seconds,
"Maniac Miner" to 59.seconds, "Maniac Miner" to 59.seconds,
"Vein Seeker" to 60.seconds "Vein Seeker" to 60.seconds
) )
override val config: ManagedConfig override val config: ManagedConfig
get() = TConfig get() = TConfig
fun getCooldownPercentage(name: String, cooldown: Duration): Double { fun getCooldownPercentage(name: String, cooldown: Duration): Double {
val sinceLastUsage = lastUsage[name]?.passedTime() ?: Duration.INFINITE val sinceLastUsage = lastUsage[name]?.passedTime() ?: Duration.INFINITE
val sinceLobbyJoin = lobbyJoinTime.passedTime() val sinceLobbyJoin = lobbyJoinTime.passedTime()
if (SBData.skyblockLocation == SkyBlockIsland.MINESHAFT) { if (SBData.skyblockLocation == SkyBlockIsland.MINESHAFT) {
if (sinceLobbyJoin < sinceLastUsage) { if (sinceLobbyJoin < sinceLastUsage) {
return 1.0 return 1.0
} }
} }
if (sinceLastUsage < cooldown) if (sinceLastUsage < cooldown)
return sinceLastUsage / cooldown return sinceLastUsage / cooldown
return 1.0 return 1.0
} }
@Subscribe @Subscribe
fun onSlotClick(it: SlotClickEvent) { fun onSlotClick(it: SlotClickEvent) {
if (MC.screen?.title?.unformattedString == "Heart of the Mountain") { if (MC.screen?.title?.unformattedString == "Heart of the Mountain") {
val name = it.stack.displayNameAccordingToNbt?.unformattedString ?: return val name = it.stack.displayNameAccordingToNbt.unformattedString
val cooldown = it.stack.loreAccordingToNbt.firstNotNullOfOrNull { val cooldown = it.stack.loreAccordingToNbt.firstNotNullOfOrNull {
cooldownPattern.useMatch(it.unformattedString) { cooldownPattern.useMatch(it.unformattedString) {
parseTimePattern(group("cooldown")) parseTimePattern(group("cooldown"))
} }
} ?: return } ?: return
defaultAbilityDurations[name] = cooldown defaultAbilityDurations[name] = cooldown
} }
} }
@Subscribe @Subscribe
fun onDurabilityBar(it: DurabilityBarEvent) { fun onDurabilityBar(it: DurabilityBarEvent) {
if (!TConfig.drillFuelBar) return if (!TConfig.drillFuelBar) return
val lore = it.item.loreAccordingToNbt val lore = it.item.loreAccordingToNbt
if (lore.lastOrNull()?.unformattedString?.contains("DRILL") != true) return if (lore.lastOrNull()?.unformattedString?.contains("DRILL") != true) return
val maxFuel = lore.firstNotNullOfOrNull { val maxFuel = lore.firstNotNullOfOrNull {
fuelPattern.useMatch(it.unformattedString) { fuelPattern.useMatch(it.unformattedString) {
parseShortNumber(group("maxFuel")) parseShortNumber(group("maxFuel"))
} }
} ?: return } ?: return
val extra = it.item.extraAttributes val extra = it.item.extraAttributes
if (!extra.contains("drill_fuel")) return if (!extra.contains("drill_fuel")) return
val fuel = extra.getInt("drill_fuel") val fuel = extra.getInt("drill_fuel")
val percentage = fuel / maxFuel.toFloat() val percentage = fuel / maxFuel.toFloat()
it.barOverride = DurabilityBarEvent.DurabilityBar( it.barOverride = DurabilityBarEvent.DurabilityBar(
lerp( lerp(
DyeColor.RED.toShedaniel(), DyeColor.RED.toShedaniel(),
DyeColor.GREEN.toShedaniel(), DyeColor.GREEN.toShedaniel(),
percentage percentage
), percentage ), percentage
) )
} }
@Subscribe @Subscribe
fun onChatMessage(it: ProcessChatEvent) { fun onChatMessage(it: ProcessChatEvent) {
abilityUsePattern.useMatch(it.unformattedString) { abilityUsePattern.useMatch(it.unformattedString) {
lastUsage[group("name")] = TimeMark.now() lastUsage[group("name")] = TimeMark.now()
} }
abilitySwitchPattern.useMatch(it.unformattedString) { abilitySwitchPattern.useMatch(it.unformattedString) {
abilityOverride = group("ability") abilityOverride = group("ability")
} }
} }
@Subscribe @Subscribe
fun onWorldReady(event: WorldReadyEvent) { fun onWorldReady(event: WorldReadyEvent) {
lobbyJoinTime = TimeMark.now() lobbyJoinTime = TimeMark.now()
abilityOverride = null abilityOverride = null
} }
@Subscribe @Subscribe
fun onProfileSwitch(event: ProfileSwitchEvent) { fun onProfileSwitch(event: ProfileSwitchEvent) {
lastUsage.clear() lastUsage.clear()
} }
val abilityUsePattern = Pattern.compile("You used your (?<name>.*) Pickaxe Ability!") val abilityUsePattern = Pattern.compile("You used your (?<name>.*) Pickaxe Ability!")
val fuelPattern = Pattern.compile("Fuel: .*/(?<maxFuel>$SHORT_NUMBER_FORMAT)") val fuelPattern = Pattern.compile("Fuel: .*/(?<maxFuel>$SHORT_NUMBER_FORMAT)")
val pickaxeAbilityCooldownPattern = Pattern.compile("Your pickaxe ability is on cooldown for (?<remainingCooldown>$TIME_PATTERN)\\.")
data class PickaxeAbilityData( data class PickaxeAbilityData(
val name: String, val name: String,
val cooldown: Duration, val cooldown: Duration,
) )
fun getCooldownFromLore(itemStack: ItemStack): PickaxeAbilityData? { fun getCooldownFromLore(itemStack: ItemStack): PickaxeAbilityData? {
val lore = itemStack.loreAccordingToNbt val lore = itemStack.loreAccordingToNbt
if (!lore.any { it.unformattedString.contains("Breaking Power") }) if (!lore.any { it.unformattedString.contains("Breaking Power") })
return null return null
val cooldown = lore.firstNotNullOfOrNull { val ability = AbilityUtils.getAbilities(itemStack).firstOrNull() ?: return null
cooldownPattern.useMatch(it.unformattedString) { return PickaxeAbilityData(ability.name, ability.cooldown ?: return null)
parseTimePattern(group("cooldown")) }
}
} ?: return null
val name = lore.firstNotNullOfOrNull {
abilityPattern.useMatch(it.unformattedString) {
group("name")
}
} ?: return null
return PickaxeAbilityData(name, cooldown)
}
val cooldownPattern = Pattern.compile("Cooldown: (?<cooldown>$TIME_PATTERN)")
val abilitySwitchPattern =
Pattern.compile("You selected (?<ability>.*) as your Pickaxe Ability\\. This ability will apply to all of your pickaxes!")
val cooldownPattern = Pattern.compile("Cooldown: (?<cooldown>$TIME_PATTERN)") @Subscribe
val abilityPattern = Pattern.compile("(⦾ )?Ability: (?<name>.*) {2}RIGHT CLICK") fun renderHud(event: HudRenderEvent) {
val abilitySwitchPattern = if (!TConfig.cooldownEnabled) return
Pattern.compile("You selected (?<ability>.*) as your Pickaxe Ability\\. This ability will apply to all of your pickaxes!") if (!event.isRenderingCursor) return
var ability = getCooldownFromLore(MC.player?.getStackInHand(Hand.MAIN_HAND) ?: return) ?: return
defaultAbilityDurations[ability.name] = ability.cooldown
@Subscribe val ao = abilityOverride
fun renderHud(event: HudRenderEvent) { if (ao != ability.name && ao != null) {
if (!TConfig.cooldownEnabled) return ability = PickaxeAbilityData(ao, defaultAbilityDurations[ao] ?: 120.seconds)
var ability = getCooldownFromLore(MC.player?.getStackInHand(Hand.MAIN_HAND) ?: return) ?: return }
defaultAbilityDurations[ability.name] = ability.cooldown event.context.matrices.push()
val ao = abilityOverride event.context.matrices.translate(MC.window.scaledWidth / 2F, MC.window.scaledHeight / 2F, 0F)
if (ao != ability.name && ao != null) { event.context.matrices.scale(TConfig.cooldownScale.toFloat(), TConfig.cooldownScale.toFloat(), 1F)
ability = PickaxeAbilityData(ao, defaultAbilityDurations[ao] ?: 120.seconds) RenderCircleProgress.renderCircle(
} event.context, Identifier.of("firmament", "textures/gui/circle.png"),
event.context.matrices.push() getCooldownPercentage(ability.name, ability.cooldown).toFloat(),
event.context.matrices.translate(MC.window.scaledWidth / 2F, MC.window.scaledHeight / 2F, 0F) 0f, 1f, 0f, 1f
event.context.matrices.scale(TConfig.cooldownScale.toFloat(), TConfig.cooldownScale.toFloat(), 1F) )
RenderCircleProgress.renderCircle( event.context.matrices.pop()
event.context, Identifier.of("firmament", "textures/gui/circle.png"), }
getCooldownPercentage(ability.name, ability.cooldown).toFloat(),
0f, 1f, 0f, 1f
)
event.context.matrices.pop()
}
} }

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.features.texturepack package moe.nea.firmament.features.texturepack
import com.google.gson.JsonElement import com.google.gson.JsonElement
@@ -6,120 +5,120 @@ import com.google.gson.JsonPrimitive
import moe.nea.firmament.util.useMatch import moe.nea.firmament.util.useMatch
abstract class NumberMatcher { abstract class NumberMatcher {
abstract fun test(number: Number): Boolean abstract fun test(number: Number): Boolean
companion object { companion object {
fun parse(jsonElement: JsonElement): NumberMatcher? { fun parse(jsonElement: JsonElement): NumberMatcher? {
if (jsonElement is JsonPrimitive) { if (jsonElement is JsonPrimitive) {
if (jsonElement.isString) { if (jsonElement.isString) {
val string = jsonElement.asString val string = jsonElement.asString
return parseRange(string) ?: parseOperator(string) return parseRange(string) ?: parseOperator(string)
} }
if (jsonElement.isNumber) { if (jsonElement.isNumber) {
val number = jsonElement.asNumber val number = jsonElement.asNumber
val hasDecimals = (number.toString().contains(".")) val hasDecimals = (number.toString().contains("."))
return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble()) return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble())
} }
} }
return null return null
} }
private val intervalSpec = private val intervalSpec =
"(?<beginningOpen>[\\[\\(])(?<beginning>[0-9.]+)?,(?<ending>[0-9.]+)?(?<endingOpen>[\\]\\)])" "(?<beginningOpen>[\\[\\(])(?<beginning>[0-9.]+)?,(?<ending>[0-9.]+)?(?<endingOpen>[\\]\\)])"
.toPattern() .toPattern()
fun parseRange(string: String): RangeMatcher? { fun parseRange(string: String): RangeMatcher? {
intervalSpec.useMatch<Nothing>(string) { intervalSpec.useMatch<Nothing>(string) {
// Open in the set-theory sense, meaning does not include its end. // Open in the set-theory sense, meaning does not include its end.
val beginningOpen = group("beginningOpen") == "(" val beginningOpen = group("beginningOpen") == "("
val endingOpen = group("endingOpen") == ")" val endingOpen = group("endingOpen") == ")"
val beginning = group("beginning")?.toDouble() val beginning = group("beginning")?.toDouble()
val ending = group("ending")?.toDouble() val ending = group("ending")?.toDouble()
return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen) return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen)
} }
return null return null
} }
enum class Operator(val operator: String) { enum class Operator(val operator: String) {
LESS("<") { LESS("<") {
override fun matches(comparisonResult: Int): Boolean { override fun matches(comparisonResult: Int): Boolean {
return comparisonResult < 0 return comparisonResult < 0
} }
}, },
LESS_EQUALS("<=") { LESS_EQUALS("<=") {
override fun matches(comparisonResult: Int): Boolean { override fun matches(comparisonResult: Int): Boolean {
return comparisonResult <= 0 return comparisonResult <= 0
} }
}, },
GREATER(">") { GREATER(">") {
override fun matches(comparisonResult: Int): Boolean { override fun matches(comparisonResult: Int): Boolean {
return comparisonResult > 0 return comparisonResult > 0
} }
}, },
GREATER_EQUALS(">=") { GREATER_EQUALS(">=") {
override fun matches(comparisonResult: Int): Boolean { override fun matches(comparisonResult: Int): Boolean {
return comparisonResult >= 0 return comparisonResult >= 0
} }
}, },
; ;
abstract fun matches(comparisonResult: Int): Boolean abstract fun matches(comparisonResult: Int): Boolean
} }
private val operatorPattern = "(?<operator>${Operator.entries.joinToString("|") {it.operator}})(?<value>[0-9.]+)".toPattern() private val operatorPattern =
"(?<operator>${Operator.entries.joinToString("|") { it.operator }})(?<value>[0-9.]+)".toPattern()
fun parseOperator(string: String): OperatorMatcher? { fun parseOperator(string: String): OperatorMatcher? {
operatorPattern.useMatch<Nothing>(string) { return operatorPattern.useMatch(string) {
val operatorName = group("operator") val operatorName = group("operator")
val operator = Operator.entries.find { it.operator == operatorName }!! val operator = Operator.entries.find { it.operator == operatorName }!!
val value = group("value").toDouble() val value = group("value").toDouble()
return OperatorMatcher(operator, value) OperatorMatcher(operator, value)
} }
return null }
}
data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() { data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() {
override fun test(number: Number): Boolean { override fun test(number: Number): Boolean {
return operator.matches(number.toDouble().compareTo(value)) return operator.matches(number.toDouble().compareTo(value))
} }
} }
data class MatchNumberExact(val number: Number) : NumberMatcher() { data class MatchNumberExact(val number: Number) : NumberMatcher() {
override fun test(number: Number): Boolean { override fun test(number: Number): Boolean {
return when (this.number) { return when (this.number) {
is Double -> number.toDouble() == this.number.toDouble() is Double -> number.toDouble() == this.number.toDouble()
else -> number.toLong() == this.number.toLong() else -> number.toLong() == this.number.toLong()
} }
} }
} }
data class RangeMatcher( data class RangeMatcher(
val beginning: Double?, val beginning: Double?,
val beginningInclusive: Boolean, val beginningInclusive: Boolean,
val ending: Double?, val ending: Double?,
val endingInclusive: Boolean, val endingInclusive: Boolean,
) : NumberMatcher() { ) : NumberMatcher() {
override fun test(number: Number): Boolean { override fun test(number: Number): Boolean {
val value = number.toDouble() val value = number.toDouble()
if (beginning != null) { if (beginning != null) {
if (beginningInclusive) { if (beginningInclusive) {
if (value < beginning) return false if (value < beginning) return false
} else { } else {
if (value <= beginning) return false if (value <= beginning) return false
} }
} }
if (ending != null) { if (ending != null) {
if (endingInclusive) { if (endingInclusive) {
if (value > ending) return false if (value > ending) return false
} else { } else {
if (value >= ending) return false if (value >= ending) return false
} }
} }
return true return true
} }
} }
} }
} }

View File

@@ -0,0 +1,16 @@
package moe.nea.firmament.util
import moe.nea.firmament.Firmament
object ErrorUtil {
var aggressiveErrors = run {
Thread.currentThread().stackTrace.any { it.className.startsWith("org.junit.") } || Firmament.DEBUG
}
@Suppress("NOTHING_TO_INLINE") // Suppressed since i want the logger to not pick up the ErrorUtil stack-frame
inline fun softError(message: String) {
if (aggressiveErrors) error(message)
else Firmament.logger.error(message)
}
}

View File

@@ -4,7 +4,9 @@ import io.github.moulberry.repo.data.Coordinate
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.client.gui.screen.ingame.HandledScreen import net.minecraft.client.gui.screen.ingame.HandledScreen
import net.minecraft.client.option.GameOptions
import net.minecraft.client.render.WorldRenderer import net.minecraft.client.render.WorldRenderer
import net.minecraft.item.Item
import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket import net.minecraft.network.packet.c2s.play.CommandExecutionC2SPacket
import net.minecraft.registry.BuiltinRegistries import net.minecraft.registry.BuiltinRegistries
import net.minecraft.registry.RegistryKeys import net.minecraft.registry.RegistryKeys
@@ -16,79 +18,82 @@ import moe.nea.firmament.events.TickEvent
object MC { object MC {
private val messageQueue = ConcurrentLinkedQueue<Text>() private val messageQueue = ConcurrentLinkedQueue<Text>()
init { init {
TickEvent.subscribe("MC:push") { TickEvent.subscribe("MC:push") {
while (true) { while (true) {
inGameHud.chatHud.addMessage(messageQueue.poll() ?: break) inGameHud.chatHud.addMessage(messageQueue.poll() ?: break)
} }
while (true) { while (true) {
(nextTickTodos.poll() ?: break).invoke() (nextTickTodos.poll() ?: break).invoke()
} }
} }
} }
fun sendChat(text: Text) { fun sendChat(text: Text) {
if (instance.isOnThread) if (instance.isOnThread)
inGameHud.chatHud.addMessage(text) inGameHud.chatHud.addMessage(text)
else else
messageQueue.add(text) messageQueue.add(text)
} }
fun sendServerCommand(command: String) { fun sendServerCommand(command: String) {
val nh = player?.networkHandler ?: return val nh = player?.networkHandler ?: return
nh.sendPacket( nh.sendPacket(
CommandExecutionC2SPacket( CommandExecutionC2SPacket(
command, command,
) )
) )
} }
fun sendServerChat(text: String) { fun sendServerChat(text: String) {
player?.networkHandler?.sendChatMessage(text) player?.networkHandler?.sendChatMessage(text)
} }
fun sendCommand(command: String) { fun sendCommand(command: String) {
player?.networkHandler?.sendCommand(command) player?.networkHandler?.sendCommand(command)
} }
fun onMainThread(block: () -> Unit) { fun onMainThread(block: () -> Unit) {
if (instance.isOnThread) if (instance.isOnThread)
block() block()
else else
instance.send(block) instance.send(block)
} }
private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>() private val nextTickTodos = ConcurrentLinkedQueue<() -> Unit>()
fun nextTick(function: () -> Unit) { fun nextTick(function: () -> Unit) {
nextTickTodos.add(function) nextTickTodos.add(function)
} }
inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl) inline val resourceManager get() = (instance.resourceManager as ReloadableResourceManagerImpl)
inline val worldRenderer: WorldRenderer get() = instance.worldRenderer inline val worldRenderer: WorldRenderer get() = instance.worldRenderer
inline val networkHandler get() = player?.networkHandler inline val networkHandler get() = player?.networkHandler
inline val instance get() = MinecraftClient.getInstance() inline val instance get() = MinecraftClient.getInstance()
inline val keyboard get() = instance.keyboard inline val keyboard get() = instance.keyboard
inline val textureManager get() = instance.textureManager inline val interactionManager get() = instance.interactionManager
inline val inGameHud get() = instance.inGameHud inline val textureManager get() = instance.textureManager
inline val font get() = instance.textRenderer inline val options get() = instance.options
inline val soundManager get() = instance.soundManager inline val inGameHud get() = instance.inGameHud
inline val player get() = instance.player inline val font get() = instance.textRenderer
inline val camera get() = instance.cameraEntity inline val soundManager get() = instance.soundManager
inline val guiAtlasManager get() = instance.guiAtlasManager inline val player get() = instance.player
inline val world get() = instance.world inline val camera get() = instance.cameraEntity
inline var screen inline val guiAtlasManager get() = instance.guiAtlasManager
get() = instance.currentScreen inline val world get() = instance.world
set(value) = instance.setScreen(value) inline var screen
inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*> get() = instance.currentScreen
inline val window get() = instance.window set(value) = instance.setScreen(value)
inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager inline val handledScreen: HandledScreen<*>? get() = instance.currentScreen as? HandledScreen<*>
val defaultRegistries: RegistryWrapper.WrapperLookup = BuiltinRegistries.createWrapperLookup() inline val window get() = instance.window
val defaultItems = defaultRegistries.getWrapperOrThrow(RegistryKeys.ITEM) inline val currentRegistries: RegistryWrapper.WrapperLookup? get() = world?.registryManager
val defaultRegistries: RegistryWrapper.WrapperLookup = BuiltinRegistries.createWrapperLookup()
inline val currentOrDefaultRegistries get() = currentRegistries ?: defaultRegistries
val defaultItems: RegistryWrapper.Impl<Item> = defaultRegistries.getWrapperOrThrow(RegistryKeys.ITEM)
} }
val Coordinate.blockPos: BlockPos val Coordinate.blockPos: BlockPos
get() = BlockPos(x, y, z) get() = BlockPos(x, y, z)

View File

@@ -1,44 +1,52 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
class TimeMark private constructor(private val timeMark: Long) : Comparable<TimeMark> { class TimeMark private constructor(private val timeMark: Long) : Comparable<TimeMark> {
fun passedTime() = if (timeMark == 0L) Duration.INFINITE else (System.currentTimeMillis() - timeMark).milliseconds fun passedTime() =
if (timeMark == 0L) Duration.INFINITE
else (System.currentTimeMillis() - timeMark).milliseconds
operator fun minus(other: TimeMark): Duration { fun passedAt(fakeNow: TimeMark) =
if (other.timeMark == timeMark) if (timeMark == 0L) Duration.INFINITE
return 0.milliseconds else (fakeNow.timeMark - timeMark).milliseconds
if (other.timeMark == 0L)
return Duration.INFINITE
if (timeMark == 0L)
return -Duration.INFINITE
return (timeMark - other.timeMark).milliseconds
}
companion object { operator fun minus(other: TimeMark): Duration {
fun now() = TimeMark(System.currentTimeMillis()) if (other.timeMark == timeMark)
fun farPast() = TimeMark(0L) return 0.milliseconds
fun ago(timeDelta: Duration): TimeMark { if (other.timeMark == 0L)
if (timeDelta.isFinite()) { return Duration.INFINITE
return TimeMark(System.currentTimeMillis() - timeDelta.inWholeMilliseconds) if (timeMark == 0L)
} return -Duration.INFINITE
require(timeDelta.isPositive()) return (timeMark - other.timeMark).milliseconds
return farPast() }
}
}
override fun hashCode(): Int { companion object {
return timeMark.hashCode() fun now() = TimeMark(System.currentTimeMillis())
} fun farPast() = TimeMark(0L)
fun ago(timeDelta: Duration): TimeMark {
if (timeDelta.isFinite()) {
return TimeMark(System.currentTimeMillis() - timeDelta.inWholeMilliseconds)
}
require(timeDelta.isPositive())
return farPast()
}
}
override fun equals(other: Any?): Boolean { override fun hashCode(): Int {
return other is TimeMark && other.timeMark == timeMark return timeMark.hashCode()
} }
override fun compareTo(other: TimeMark): Int { override fun equals(other: Any?): Boolean {
return this.timeMark.compareTo(other.timeMark) return other is TimeMark && other.timeMark == timeMark
} }
override fun toString(): String {
return "https://time.is/$timeMark"
}
override fun compareTo(other: TimeMark): Int {
return this.timeMark.compareTo(other.timeMark)
}
} }

View File

@@ -0,0 +1,138 @@
package moe.nea.firmament.util.mc
import net.minecraft.nbt.NbtByte
import net.minecraft.nbt.NbtByteArray
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtDouble
import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtEnd
import net.minecraft.nbt.NbtFloat
import net.minecraft.nbt.NbtInt
import net.minecraft.nbt.NbtIntArray
import net.minecraft.nbt.NbtList
import net.minecraft.nbt.NbtLong
import net.minecraft.nbt.NbtLongArray
import net.minecraft.nbt.NbtShort
import net.minecraft.nbt.NbtString
import net.minecraft.nbt.visitor.NbtElementVisitor
class SNbtFormatter private constructor() : NbtElementVisitor {
private val result = StringBuilder()
private var indent = 0
private fun writeIndent() {
result.append("\t".repeat(indent))
}
private fun pushIndent() {
indent++
}
private fun popIndent() {
indent--
}
fun apply(element: NbtElement): StringBuilder {
element.accept(this)
return result
}
override fun visitString(element: NbtString) {
result.append(NbtString.escape(element.asString()))
}
override fun visitByte(element: NbtByte) {
result.append(element.numberValue()).append("b")
}
override fun visitShort(element: NbtShort) {
result.append(element.shortValue()).append("s")
}
override fun visitInt(element: NbtInt) {
result.append(element.intValue())
}
override fun visitLong(element: NbtLong) {
result.append(element.longValue()).append("L")
}
override fun visitFloat(element: NbtFloat) {
result.append(element.floatValue()).append("f")
}
override fun visitDouble(element: NbtDouble) {
result.append(element.doubleValue()).append("d")
}
private fun visitArrayContents(array: List<NbtElement>) {
array.forEachIndexed { index, element ->
writeIndent()
element.accept(this)
if (array.size != index + 1) {
result.append(",")
}
result.append("\n")
}
}
private fun writeArray(arrayTypeTag: String, array: List<NbtElement>) {
result.append("[").append(arrayTypeTag).append("\n")
pushIndent()
visitArrayContents(array)
popIndent()
writeIndent()
result.append("]")
}
override fun visitByteArray(element: NbtByteArray) {
writeArray("B;", element)
}
override fun visitIntArray(element: NbtIntArray) {
writeArray("I;", element)
}
override fun visitLongArray(element: NbtLongArray) {
writeArray("L;", element)
}
override fun visitList(element: NbtList) {
writeArray("", element)
}
override fun visitCompound(compound: NbtCompound) {
result.append("{\n")
pushIndent()
val keys = compound.keys.sorted()
keys.forEachIndexed { index, key ->
writeIndent()
val element = compound[key] ?: error("Key '$key' found but not present in compound: $compound")
val escapedName = if (key.matches(SIMPLE_NAME)) key else NbtString.escape(key)
result.append(escapedName).append(": ")
element.accept(this)
if (keys.size != index + 1) {
result.append(",")
}
result.append("\n")
}
popIndent()
writeIndent()
result.append("}")
}
override fun visitEnd(element: NbtEnd) {
result.append("END")
}
companion object {
fun prettify(nbt: NbtElement): String {
return SNbtFormatter().apply(nbt).toString()
}
fun NbtElement.toPrettyString() = prettify(this)
private val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex()
}
}

View File

@@ -1,8 +1,14 @@
@file:OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class)
package moe.nea.firmament.util package moe.nea.firmament.util
import java.util.regex.Matcher import java.util.regex.Matcher
import java.util.regex.Pattern import java.util.regex.Pattern
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalTypeInference
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@@ -10,10 +16,14 @@ import kotlin.time.Duration.Companion.seconds
inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? = inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? =
regex.matchEntire(this)?.let(block) regex.matchEntire(this)?.let(block)
inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? = inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? {
matcher(string) contract {
callsInPlace(block, InvocationKind.AT_MOST_ONCE)
}
return matcher(string)
.takeIf(Matcher::matches) .takeIf(Matcher::matches)
?.let(block) ?.let(block)
}
@Language("RegExp") @Language("RegExp")
val TIME_PATTERN = "[0-9]+[ms]" val TIME_PATTERN = "[0-9]+[ms]"

View File

@@ -0,0 +1,138 @@
package moe.nea.firmament.util.skyblock
import kotlin.time.Duration
import net.minecraft.item.ItemStack
import net.minecraft.text.Text
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.directLiteralStringContent
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.parseTimePattern
import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch
object AbilityUtils {
data class ItemAbility(
val name: String,
val hasPowerScroll: Boolean,
val activation: AbilityActivation,
val manaCost: Int?,
val descriptionLines: List<Text>,
val cooldown: Duration?,
)
@JvmInline
value class AbilityActivation(
val label: String
) {
companion object {
val RIGHT_CLICK = AbilityActivation("RIGHT CLICK")
val SNEAK_RIGHT_CLICK = AbilityActivation("SNEAK RIGHT CLICK")
val SNEAK = AbilityActivation("SNEAK")
val EMPTY = AbilityActivation("")
fun of(text: String?): AbilityActivation {
val trimmed = text?.trim()
if (trimmed.isNullOrBlank())
return EMPTY
return AbilityActivation(trimmed)
}
}
}
private val abilityNameRegex = "Ability: (?<name>.*?) *".toPattern()
private fun findAbility(iterator: ListIterator<Text>): ItemAbility? {
if (!iterator.hasNext()) {
return null
}
val line = iterator.next()
// The actual information about abilities is stored in the siblings
if (line.directLiteralStringContent != "") return null
var powerScroll: Boolean = false // This should instead determine the power scroll based on text colour
var abilityName: String? = null
var activation: String? = null
var hasProcessedActivation = false
for (sibling in line.siblings) {
val directContent = sibling.directLiteralStringContent ?: continue
if (directContent == "") {
powerScroll = true
continue
}
if (!hasProcessedActivation && abilityName != null) {
hasProcessedActivation = true
activation = directContent
continue
}
abilityNameRegex.useMatch<Nothing>(directContent) {
abilityName = group("name")
continue
}
if (abilityName != null) {
ErrorUtil.softError("Found abilityName $abilityName without finding but encountered unprocessable element in: $line")
}
return null
}
if (abilityName == null) return null
val descriptionLines = mutableListOf<Text>()
var manaCost: Int? = null
var cooldown: Duration? = null
while (iterator.hasNext()) {
val descriptionLine = iterator.next()
if (descriptionLine.unformattedString == "") break
var nextIsManaCost = false
var isSpecialLine = false
var nextIsDuration = false
for (sibling in descriptionLine.siblings) {
val directContent = sibling.directLiteralStringContent ?: continue
if ("Mana Cost: " == directContent) { // TODO: 'Soulflow Cost: ' support (or maybe a generic 'XXX Cost: ')
nextIsManaCost = true
isSpecialLine = true
continue
}
if ("Cooldown: " == directContent) {
nextIsDuration = true
isSpecialLine = true
continue
}
if (nextIsDuration) {
nextIsDuration = false
cooldown = parseTimePattern(directContent)
continue
}
if (nextIsManaCost) {
nextIsManaCost = false
manaCost = parseShortNumber(directContent).toInt()
continue
}
if (isSpecialLine) {
ErrorUtil.softError("Unknown special line segment: '$sibling' in '$descriptionLine'")
}
}
if (!isSpecialLine) {
descriptionLines.add(descriptionLine)
}
}
return ItemAbility(
abilityName,
powerScroll,
AbilityActivation.of(activation),
manaCost,
descriptionLines,
cooldown
)
}
fun getAbilities(lore: List<Text>): List<ItemAbility> {
val iterator = lore.listIterator()
val abilities = mutableListOf<ItemAbility>()
while (iterator.hasNext()) {
findAbility(iterator)?.let(abilities::add)
}
return abilities
}
fun getAbilities(itemStack: ItemStack): List<ItemAbility> {
return getAbilities(itemStack.loreAccordingToNbt)
}
}

View File

@@ -90,6 +90,8 @@ fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String {
val Text.unformattedString: String val Text.unformattedString: String
get() = string.removeColorCodes() get() = string.removeColorCodes()
val Text.directLiteralStringContent: String? get() = (this.content as? PlainTextContent)?.string()
fun Text.allSiblings(): List<Text> = listOf(this) + siblings.flatMap { it.allSiblings() } fun Text.allSiblings(): List<Text> = listOf(this) + siblings.flatMap { it.allSiblings() }
fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) } fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) }

View File

@@ -182,8 +182,9 @@
"firmament.config.power-user.copy-texture-pack-id": "Copy Texture Pack Id", "firmament.config.power-user.copy-texture-pack-id": "Copy Texture Pack Id",
"firmament.config.power-user.copy-skull-texture": "Copy Placed Skull Id", "firmament.config.power-user.copy-skull-texture": "Copy Placed Skull Id",
"firmament.config.power-user.entity-data": "Show Entity Data", "firmament.config.power-user.entity-data": "Show Entity Data",
"firmament.config.power-user.copy-nbt-data": "Copy NBT data", "firmament.config.power-user.copy-nbt-data": "Copy ExtraAttributes data",
"firmament.config.power-user.copy-lore": "Copy Name + Lore", "firmament.config.power-user.copy-lore": "Copy Name + Lore",
"firmament.config.power-user.copy-item-stack": "Copy ItemStack",
"firmament.config.power-user": "Power Users", "firmament.config.power-user": "Power Users",
"firmament.tooltip.skyblockid": "SkyBlock Id: %s", "firmament.tooltip.skyblockid": "SkyBlock Id: %s",
"firmament.tooltip.copied.skyblockid.fail": "Failed to copy SkyBlock Id", "firmament.tooltip.copied.skyblockid.fail": "Failed to copy SkyBlock Id",
@@ -194,6 +195,7 @@
"firmament.tooltip.copied.skull.fail": "Failed to copy skull id.", "firmament.tooltip.copied.skull.fail": "Failed to copy skull id.",
"firmament.tooltip.copied.nbt": "Copied NBT data", "firmament.tooltip.copied.nbt": "Copied NBT data",
"firmament.tooltip.copied.lore": "Copied Name and Lore", "firmament.tooltip.copied.lore": "Copied Name and Lore",
"firmament.tooltip.copied.stack": "Copied ItemStack",
"firmament.config.compatibility": "Intermod Features", "firmament.config.compatibility": "Intermod Features",
"firmament.config.compatibility.explosion-enabled": "Redirect Enhanced Explosions", "firmament.config.compatibility.explosion-enabled": "Redirect Enhanced Explosions",
"firmament.config.compatibility.explosion-power": "Enhanced Explosion Power", "firmament.config.compatibility.explosion-power": "Enhanced Explosion Power",

View File

@@ -22,3 +22,8 @@ mutable field net/minecraft/screen/slot/Slot y I
accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData; accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData;
accessible field net/minecraft/client/render/WorldRenderer chunks Lnet/minecraft/client/render/BuiltChunkStorage; accessible field net/minecraft/client/render/WorldRenderer chunks Lnet/minecraft/client/render/BuiltChunkStorage;
# Fix package-private access methods
accessible method net/minecraft/registry/entry/RegistryEntry$Reference setRegistryKey (Lnet/minecraft/registry/RegistryKey;)V
accessible method net/minecraft/entity/LivingEntity getHitbox ()Lnet/minecraft/util/math/Box;
accessible method net/minecraft/registry/entry/RegistryEntryList$Named <init> (Lnet/minecraft/registry/entry/RegistryEntryOwner;Lnet/minecraft/registry/tag/TagKey;)V
accessible method net/minecraft/registry/entry/RegistryEntry$Reference setValue (Ljava/lang/Object;)V

29
src/test/kotlin/root.kt Normal file
View File

@@ -0,0 +1,29 @@
package moe.nea.firmament.test
import net.minecraft.Bootstrap
import net.minecraft.SharedConstants
import moe.nea.firmament.util.TimeMark
object FirmTestBootstrap {
val loadStart = TimeMark.now()
init {
println("Bootstrap started at $loadStart")
}
init {
SharedConstants.createGameVersion()
Bootstrap.initialize()
}
val loadEnd = TimeMark.now()
val loadDuration = loadStart.passedAt(loadEnd)
init {
println("Bootstrap completed at $loadEnd after $loadDuration")
}
fun bootstrapMinecraft() {
}
}

View File

@@ -0,0 +1,30 @@
package moe.nea.firmament.test.testutil
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.StringNbtReader
import moe.nea.firmament.test.FirmTestBootstrap
object ItemResources {
init {
FirmTestBootstrap.bootstrapMinecraft()
}
fun loadString(path: String): String {
require(!path.startsWith("/"))
return ItemResources::class.java.classLoader
.getResourceAsStream(path)!!
.readAllBytes().decodeToString()
}
fun loadSNbt(path: String): NbtCompound {
return StringNbtReader.parse(loadString(path))
}
fun loadItem(name: String): ItemStack {
// TODO: make the load work with enchantments
return ItemStack.CODEC.parse(NbtOps.INSTANCE, loadSNbt("testdata/items/$name.snbt"))
.getOrThrow { IllegalStateException("Could not load test item '$name': $it") }
}
}

View File

@@ -1,12 +1,13 @@
package moe.nea.firmament.test.util
package moe.nea.firmament.test
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import net.minecraft.Bootstrap
import net.minecraft.SharedConstants
import moe.nea.firmament.util.removeColorCodes import moe.nea.firmament.util.removeColorCodes
class ColorCode { class ColorCodeTest {
@Test @Test
fun testWhatever() { fun testWhatever() {
Assertions.assertEquals("", "".removeColorCodes().toString()) Assertions.assertEquals("", "".removeColorCodes().toString())

View File

@@ -0,0 +1,79 @@
package moe.nea.firmament.test.util.skyblock
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
import moe.nea.firmament.test.testutil.ItemResources
import moe.nea.firmament.util.skyblock.AbilityUtils
import moe.nea.firmament.util.unformattedString
class AbilityUtilsTest {
fun List<AbilityUtils.ItemAbility>.stripDescriptions() = map {
it.copy(descriptionLines = it.descriptionLines.map { Text.literal(it.unformattedString) })
}
@Test
fun testUnpoweredDrill() {
Assertions.assertEquals(
listOf(
AbilityUtils.ItemAbility(
"Pickobulus",
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),
48.seconds
)
),
AbilityUtils.getAbilities(ItemResources.loadItem("titanium-drill")).stripDescriptions()
)
}
@Test
fun testPoweredPickaxe() {
Assertions.assertEquals(
listOf(
AbilityUtils.ItemAbility(
"Mining Speed Boost",
true,
AbilityUtils.AbilityActivation.RIGHT_CLICK,
null,
listOf("Grants +200% ⸕ Mining Speed for",
"10s.").map(Text::literal),
2.minutes
)
),
AbilityUtils.getAbilities(ItemResources.loadItem("diamond-pickaxe")).stripDescriptions()
)
}
@Test
fun testAOTV() {
Assertions.assertEquals(
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),
null
),
AbilityUtils.ItemAbility(
"Ether Transmission",
false,
AbilityUtils.AbilityActivation.SNEAK_RIGHT_CLICK,
90,
listOf("Teleport to your targeted block up",
"to 61 blocks away.",
"Soulflow Cost: 1").map(Text::literal),
null
)
),
AbilityUtils.getAbilities(ItemResources.loadItem("aspect-of-the-void")).stripDescriptions()
)
}
}

View File

@@ -0,0 +1,59 @@
{
components: {
"minecraft:attribute_modifiers": {
modifiers: [
],
show_in_tooltip: 0b
},
"minecraft:custom_data": {
donated_museum: 1b,
enchantments: {
ultimate_wise: 5
},
ethermerge: 1b,
gems: {
},
id: "ASPECT_OF_THE_VOID",
modifier: "heroic",
originTag: "ASPECT_OF_THE_VOID",
power_ability_scroll: "SAPPHIRE_POWER_SCROLL",
timestamp: 1641640380000L,
tuned_transmission: 4,
uuid: "b0572534-eb14-46cd-90c6-0df878fd56a2"
},
"minecraft:custom_name": '{"extra":[{"color":"dark_purple","text":"Heroic Aspect of the Void"}],"italic":false,"text":""}',
"minecraft:enchantment_glint_override": 1b,
"minecraft:hide_additional_tooltip": {
},
"minecraft:lore": [
'{"extra":[{"color":"gray","text":"Damage: "},{"color":"red","text":"+120"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Strength: "},{"color":"red","text":"+132 "},{"color":"blue","text":"(+32)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Bonus Attack Speed: "},{"color":"red","text":"+3% "},{"color":"blue","text":"(+3%)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Intelligence: "},{"color":"green","text":"+80 "},{"color":"blue","text":"(+80)"}],"italic":false,"text":""}',
'{"extra":[" ",{"color":"dark_gray","text":"["},{"color":"gray","text":"✎"},{"color":"dark_gray","text":"]"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":""},{"bold":true,"color":"light_purple","text":"Ultimate Wise V"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Reduces the ability mana cost of this"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"item by "},{"color":"green","text":"50%"},{"color":"gray","text":"."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"aqua","text":"⦾ "},{"color":"gold","text":"Ability: Instant Transmission "},{"bold":true,"color":"yellow","text":"RIGHT CLICK"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Teleport "},{"color":"green","text":"12 blocks"},{"color":"gray","text":" ahead of you and"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"gain "},{"color":"green","text":"+50 "},{"color":"white","text":"✦ Speed"},{"color":"gray","text":" for "},{"color":"green","text":"3 seconds"},{"color":"gray","text":"."}],"italic":false,"text":""}',
'{"extra":[{"color":"dark_gray","text":"Mana Cost: "},{"color":"dark_aqua","text":"23"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gold","text":"Ability: Ether Transmission "},{"bold":true,"color":"yellow","text":"SNEAK RIGHT CLICK"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Teleport to your targeted block up"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"to "},{"color":"green","text":"61 blocks "},{"color":"gray","text":"away."}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"Soulflow Cost: "},{"color":"dark_aqua","text":"1"}],"italic":false,"text":""}',
'{"extra":[{"color":"dark_gray","text":"Mana Cost: "},{"color":"dark_aqua","text":"90"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"dark_gray","text":"* "},{"color":"dark_gray","text":"Co-op Soulbound "},{"bold":true,"color":"dark_gray","text":"*"}],"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"dark_purple","text":"EPIC SWORD"}],"italic":false,"text":""}'
],
"minecraft:unbreakable": {
show_in_tooltip: 0b
}
},
count: 1,
id: "minecraft:diamond_shovel"
}

View File

@@ -0,0 +1,48 @@
{
components: {
"minecraft:attribute_modifiers": {
modifiers: [
],
show_in_tooltip: 0b
},
"minecraft:custom_data": {
enchantments: {
efficiency: 10
},
id: "DIAMOND_PICKAXE",
power_ability_scroll: "SAPPHIRE_POWER_SCROLL",
timestamp: 1659795180000L,
uuid: "d213f48e-d927-4748-a58c-eb80735025b7"
},
"minecraft:custom_name": '{"extra":[{"color":"green","text":"Diamond Pickaxe"}],"italic":false,"text":""}',
"minecraft:enchantments": {
levels: {
}
},
"minecraft:hide_additional_tooltip": {
},
"minecraft:lore": [
'{"extra":[{"color":"dark_gray","text":"Breaking Power 4"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Damage: "},{"color":"red","text":"+30"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Mining Speed: "},{"color":"green","text":"+220"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Efficiency X"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Increases how quickly your tool"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"breaks blocks."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"aqua","text":"⦾ "},{"color":"gold","text":"Ability: Mining Speed Boost "},{"bold":true,"color":"yellow","text":"RIGHT CLICK"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Grants "},{"color":"gold","text":"+200% "},{"color":"gold","text":"⸕ Mining Speed "},{"color":"gray","text":"for"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"green","text":"10s"},{"color":"gray","text":"."}],"italic":false,"text":""}',
'{"extra":[{"color":"dark_gray","text":"Cooldown: "},{"color":"green","text":"120s"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"This item can be reforged!"}],"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"green","text":"UNCOMMON PICKAXE"}],"italic":false,"text":""}'
],
"minecraft:unbreakable": {
show_in_tooltip: 0b
}
},
count: 1,
id: "minecraft:diamond_pickaxe"
}

View File

@@ -0,0 +1,97 @@
{
components: {
"minecraft:attribute_modifiers": {
modifiers: [
],
show_in_tooltip: 0b
},
"minecraft:custom_data": {
compact_blocks: 1023815,
donated_museum: 1b,
drill_fuel: 16621,
drill_part_fuel_tank: "titanium_fuel_tank",
drill_part_upgrade_module: "goblin_omelette_blue_cheese",
enchantments: {
compact: 10,
efficiency: 5,
experience: 3,
fortune: 3,
paleontologist: 2,
pristine: 5
},
gems: {
AMBER_0: {
quality: "PERFECT",
uuid: "d28be6ae-75eb-49e4-90d8-31759db18d79"
},
JADE_0: {
quality: "PERFECT",
uuid: "657fea0b-88e2-483d-9d2c-0b821797a55a"
},
MINING_0: {
quality: "PERFECT",
uuid: "257bdcd2-585b-48b9-9517-a2e841dc0574"
},
MINING_0_gem: "TOPAZ",
unlocked_slots: [
"JADE_0",
"MINING_0"
]
},
id: "TITANIUM_DRILL_4",
modifier: "auspicious",
rarity_upgrades: 1,
timestamp: 1700577120000L,
uuid: "367b85ab-5bb4-43b6-a055-084cbaaafc1c"
},
"minecraft:custom_name": '{"extra":[{"color":"light_purple","text":"Auspicious Titanium Drill DR-X655"}],"italic":false,"text":""}',
"minecraft:enchantment_glint_override": 1b,
"minecraft:hide_additional_tooltip": {
},
"minecraft:lore": [
'{"extra":[{"color":"dark_gray","text":"Breaking Power 9"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Damage: "},{"color":"red","text":"+75"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Mining Speed: "},{"color":"green","text":"+1,885 "},{"color":"blue","text":"(+75) "},{"color":"light_purple","text":"(+100)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Pristine: "},{"color":"green","text":"+4.5 "},{"color":"light_purple","text":"(+2)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Mining Fortune: "},{"color":"green","text":"+220 "},{"color":"blue","text":"(+20) "},{"color":"light_purple","text":"(+50)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Mining Wisdom: "},{"color":"green","text":"+10"}],"italic":false,"text":""}',
'{"extra":[" ",{"color":"gold","text":"["},{"color":"gold","text":"⸕"},{"color":"gold","text":"] "},{"color":"gold","text":"["},{"color":"green","text":"☘"},{"color":"gold","text":"] "},{"color":"gold","text":"["},{"color":"yellow","text":"✦"},{"color":"gold","text":"]"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Compact X"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Efficiency V"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Experience III"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Fortune III"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Paleontologist II"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Prismatic V"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"green","text":"Titanium-Infused Fuel Tank."}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"gray","text":""},{"color":"dark_green","text":"25,000 Max Fuel Capacity."}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"gray","text":""},{"color":"green","text":"-4% Pickaxe Ability Cooldown."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Drill Engine: "},{"color":"red","text":"Not Installed"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Increases "},{"color":"gold","text":"⸕ Mining Speed "},{"color":"gray","text":"with part"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"installed."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"green","text":"Blue Cheese Goblin Omelette Part."}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Adds "},{"color":"green","text":"+1 Level "},{"color":"gray","text":"to all of your unlocked "},{"color":"dark_purple","text":"Heart of"}],"italic":false,"text":""}',
'{"extra":[{"color":"dark_purple","text":"the Mountain "},{"color":"gray","text":"perks."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Fuel: "},{"color":"dark_green","text":"16,621"},{"color":"dark_gray","text":"/25k"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gold","text":"Ability: Pickobulus "},{"bold":true,"color":"yellow","text":"RIGHT CLICK"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Throw your pickaxe to create an"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"explosion mining all ores in a "},{"color":"green","text":"3 "},{"color":"gray","text":"block"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"radius."}],"italic":false,"text":""}',
'{"extra":[{"color":"dark_gray","text":"Cooldown: "},{"color":"green","text":"48s"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Auspicious Bonus"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Grants "},{"color":"gold","text":"+0.9% "},{"color":"gold","text":"☘ Mining Fortune"},{"color":"gray","text":"."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"dark_gray","text":"* "},{"color":"dark_gray","text":"Co-op Soulbound "},{"bold":true,"color":"dark_gray","text":"*"}],"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"},"",{"bold":false,"extra":[" "],"italic":false,"obfuscated":false,"strikethrough":false,"text":"","underlined":false},{"bold":true,"color":"light_purple","text":"MYTHIC DRILL "},{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"}],"italic":false,"text":""}'
]
},
count: 1,
id: "minecraft:prismarine_shard"
}

View File

@@ -0,0 +1,80 @@
package moe.nea.firmament.annotations.process
import com.google.auto.service.AutoService
import com.google.devtools.ksp.containingFile
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import java.io.OutputStreamWriter
import java.nio.charset.StandardCharsets
import java.util.TreeSet
class GameTestContainingClassProcessor(
val logger: KSPLogger,
val codeGenerator: CodeGenerator,
val sourceSetName: String,
) : SymbolProcessor {
@AutoService(SymbolProcessorProvider::class)
class Provider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return GameTestContainingClassProcessor(
environment.logger,
environment.codeGenerator,
environment.options["firmament.sourceset"] ?: "main")
}
}
val allClasses: MutableSet<String> = TreeSet()
val allSources = mutableSetOf<KSFile>()
override fun process(resolver: Resolver): List<KSAnnotated> {
val annotated = resolver.getSymbolsWithAnnotation("net.minecraft.test.GameTest").toList()
annotated.forEach {
val containingClass = it.parent as KSClassDeclaration
allClasses.add(containingClass.qualifiedName!!.asString())
allSources.add(it.containingFile!!)
}
return emptyList()
}
fun createJson(): JsonObject {
return JsonObject().apply {
addProperty("schemaVersion", 1)
addProperty("id", "firmament-gametest")
addProperty("name", "Firmament Gametest")
addProperty("version", "1.0.0")
addProperty("environment", "*")
add("entrypoints", JsonObject().apply {
add("fabric-gametest", JsonArray().apply {
allClasses.forEach {
add(it)
}
})
})
}
}
override fun finish() {
if (allClasses.isEmpty()) return
val stream = codeGenerator.createNewFile(Dependencies(aggregating = true, *allSources.toTypedArray()),
"",
"fabric.mod",
"json")
val output = OutputStreamWriter(stream, StandardCharsets.UTF_8)
Gson().toJson(createJson(), output)
output.close()
}
}