diff options
author | nea <nea@nea.moe> | 2023-08-06 23:03:06 +0200 |
---|---|---|
committer | hannibal2 <24389977+hannibal002@users.noreply.github.com> | 2023-08-07 00:53:31 +0200 |
commit | 1da07465b60a8e357373d7c3a3f764dfa4d5960b (patch) | |
tree | bc94e3caa665223ef273e6166c3d9746af23970a /src | |
parent | fa464f1d1eef4e1d320c47350fe6ded16b36b3ed (diff) | |
download | skyhanni-1da07465b60a8e357373d7c3a3f764dfa4d5960b.tar.gz skyhanni-1da07465b60a8e357373d7c3a3f764dfa4d5960b.tar.bz2 skyhanni-1da07465b60a8e357373d7c3a3f764dfa4d5960b.zip |
Basic test for dungeon items
Diffstat (limited to 'src')
14 files changed, 303 insertions, 4 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt index 33cad95fd..51e6c2c3c 100644 --- a/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt +++ b/src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt @@ -100,6 +100,7 @@ import at.hannibal2.skyhanni.mixins.hooks.RenderLivingEntityHelper import at.hannibal2.skyhanni.test.* import at.hannibal2.skyhanni.test.command.CopyNearbyParticlesCommand import at.hannibal2.skyhanni.utils.MinecraftConsoleFilter.Companion.initLogging +import at.hannibal2.skyhanni.utils.NEUItems import at.hannibal2.skyhanni.utils.NEUVersionCheck.checkIfNeuIsLoaded import at.hannibal2.skyhanni.utils.TabListData import kotlinx.coroutines.CoroutineName @@ -141,6 +142,7 @@ class SkyHanniMod { loadModule(ItemRenderBackground()) loadModule(EntityData()) loadModule(EntityMovementData()) + loadModule(TestExportTools) loadModule(ItemClickData()) loadModule(MinecraftData()) loadModule(TitleUtils()) diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/DevConfig.java b/src/main/java/at/hannibal2/skyhanni/config/features/DevConfig.java index 459eb65a6..b470574bc 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/DevConfig.java +++ b/src/main/java/at/hannibal2/skyhanni/config/features/DevConfig.java @@ -64,6 +64,12 @@ public class DevConfig { public boolean showItemUuid = false; @Expose + @ConfigOption(name = "Copy NBT data", desc = "Copies compressed NBT data on key press in a GUI") + @ConfigEditorKeybind(defaultKey = Keyboard.KEY_NONE) + @ConfigAccordionId(id = 0) + public int copyNBTDataCompressed = Keyboard.KEY_NONE; + + @Expose @ConfigOption(name = "Copy Rng Meter", desc = "Copies internal names and maxed xp needed from rng meter inventories in json format into the clipboard.") @ConfigEditorBoolean @ConfigAccordionId(id = 0) diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValue.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValue.kt index 743cef9c8..ea9171b8e 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValue.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValue.kt @@ -31,7 +31,7 @@ import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getReforgeName import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getRune import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getSilexCount import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getTransmissionTunerCount -import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.hasArtOfPiece +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.hasArtOfPeace import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.hasArtOfWar import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.hasBookOfStats import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.hasEtherwarp @@ -314,7 +314,7 @@ object EstimatedItemValue { // TODO untested private fun addArtOfPiece(stack: ItemStack, list: MutableList<String>): Double { - if (!stack.hasArtOfPiece()) return 0.0 + if (!stack.hasArtOfPeace()) return 0.0 val ripTechno = "THE_ART_OF_PEACE" val price = NEUItems.getPrice(ripTechno) diff --git a/src/main/java/at/hannibal2/skyhanni/test/TestExportTools.kt b/src/main/java/at/hannibal2/skyhanni/test/TestExportTools.kt new file mode 100644 index 000000000..7ac530452 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/test/TestExportTools.kt @@ -0,0 +1,59 @@ +package at.hannibal2.skyhanni.test + +import at.hannibal2.skyhanni.SkyHanniMod +import at.hannibal2.skyhanni.utils.* +import com.google.gson.GsonBuilder +import com.google.gson.JsonElement +import net.minecraft.client.gui.inventory.GuiContainer +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound +import net.minecraftforge.client.event.GuiScreenEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import org.lwjgl.input.Keyboard +import java.io.InputStreamReader +import java.io.Reader + +object TestExportTools { + val gson = GsonBuilder() + .registerTypeAdapterFactory(KotlinTypeAdapterFactory()) + .registerTypeAdapter(NBTTagCompound::class.java, NBTTypeAdapter) + .registerTypeAdapterFactory(ItemStackTypeAdapterFactory) + .create() + + class Key<T> internal constructor(val name: String) + + val Item = Key<ItemStack>("Item") + + @KSerializable + data class TestValue( + val type: String, + val data: JsonElement, + ) + + fun <T> toJson(key: Key<T>, value: T): String { + return gson.toJson(TestValue(key.name, gson.toJsonTree(value))) + } + + inline fun <reified T> fromJson(key: Key<T>, reader: Reader): T { + val serializable = gson.fromJson<TestValue>(reader) + require(key.name == serializable.type) + return gson.fromJson(serializable.data) + } + + @SubscribeEvent + fun onKeybind(event: GuiScreenEvent.KeyboardInputEvent) { + if (!Keyboard.getEventKeyState() || Keyboard.getEventKey() != SkyHanniMod.feature.dev.copyNBTDataCompressed) return + val gui = event.gui as? GuiContainer ?: return + val focussedSlot = gui.slotUnderMouse ?: return + val stack = focussedSlot.stack ?: return + val json = toJson(Item, stack) + OSUtils.copyToClipboard(json) + LorenzUtils.chat("Copied test importable to clipbooard") + } + + + inline fun <reified T> getTestData(category: Key<T>, name: String): T { + val reader = InputStreamReader(javaClass.getResourceAsStream("/testdata/${category.name}/$name.json")!!) + return fromJson(category, reader) + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ItemStackTypeAdapter.kt b/src/main/java/at/hannibal2/skyhanni/utils/ItemStackTypeAdapter.kt new file mode 100644 index 000000000..38002990f --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemStackTypeAdapter.kt @@ -0,0 +1,29 @@ +package at.hannibal2.skyhanni.utils + +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound + +object ItemStackTypeAdapterFactory : TypeAdapterFactory { + override fun <T : Any> create(gson: Gson?, type: TypeToken<T>): TypeAdapter<T>? { + if (type.rawType == ItemStack::class.java) { + val nbtCompoundTypeAdapter = gson!!.getAdapter(NBTTagCompound::class.java) + return object : TypeAdapter<ItemStack>() { + override fun write(out: JsonWriter, value: ItemStack) { + nbtCompoundTypeAdapter.write(out, value.serializeNBT()) + } + + override fun read(reader: JsonReader): ItemStack { + return ItemStack.loadItemStackFromNBT(nbtCompoundTypeAdapter.read(reader)) + } + } as TypeAdapter<T> + } + return null + } + +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt index 3df9e4673..5b9328682 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt @@ -122,7 +122,7 @@ object ItemUtils { fun ItemStack.getInternalName_new() = NEUInternalName.from(getInternalName()) fun ItemStack.getInternalName(): String { - if (name == "§fWisp's Ice-Flavored Water I Splash Potion") { + if (tagCompound?.getCompoundTag("display")?.getString("Name") == "§fWisp's Ice-Flavored Water I Splash Potion") { return "WISP_POTION" } return NEUItems.getInternalName(this) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/JsonUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/JsonUtils.kt new file mode 100644 index 000000000..94a162c98 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/JsonUtils.kt @@ -0,0 +1,11 @@ +package at.hannibal2.skyhanni.utils + +import com.google.gson.Gson +import com.google.gson.JsonElement +import java.io.Reader +import kotlin.reflect.jvm.javaType +import kotlin.reflect.typeOf + +inline fun <reified T : Any> Gson.fromJson(string: String): T = this.fromJson(string, typeOf<T>().javaType) +inline fun <reified T : Any> Gson.fromJson(jsonElement: JsonElement): T = this.fromJson(jsonElement, typeOf<T>().javaType) +inline fun <reified T : Any> Gson.fromJson(reader: Reader): T = this.fromJson(reader, typeOf<T>().javaType) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/KSerializable.kt b/src/main/java/at/hannibal2/skyhanni/utils/KSerializable.kt new file mode 100644 index 000000000..d2f608ffa --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/KSerializable.kt @@ -0,0 +1,109 @@ +package at.hannibal2.skyhanni.utils + +import com.google.gson.Gson +import com.google.gson.JsonElement +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.annotations.SerializedName +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter +import kotlin.reflect.* +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.isSubtypeOf +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor +import com.google.gson.internal.`$Gson$Types` as InternalGsonTypes + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS) +annotation class KSerializable + + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS) +annotation class ExtraData + + +class KotlinTypeAdapterFactory : TypeAdapterFactory { + + internal data class ParameterInfo( + val param: KParameter, + val adapter: TypeAdapter<Any?>, + val name: String, + val field: KProperty1<Any, Any?> + ) + + @OptIn(ExperimentalStdlibApi::class) + override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { + val kotlinClass = type.rawType.kotlin as KClass<T> + if (kotlinClass.findAnnotation<KSerializable>() == null) return null + if (!kotlinClass.isData) return null + val primaryConstructor = kotlinClass.primaryConstructor ?: return null + val params = primaryConstructor.parameters.filter { it.findAnnotation<ExtraData>() == null } + val extraDataParam = primaryConstructor.parameters + .find { it.findAnnotation<ExtraData>() != null && typeOf<MutableMap<String, JsonElement>>().isSubtypeOf(it.type) } + ?.let { param -> + param to kotlinClass.memberProperties.find { it.name == param.name && it.returnType.isSubtypeOf(typeOf<Map<String, JsonElement>>()) } as KProperty1<Any, Map<String, JsonElement>> + } + val parameterInfos = params.map { param -> + ParameterInfo( + param, + gson.getAdapter( + TypeToken.get(InternalGsonTypes.resolve(type.type, type.rawType, param.type.javaType)) + ) as TypeAdapter<Any?>, + param.findAnnotation<SerializedName>()?.value ?: param.name!!, + kotlinClass.memberProperties.find { it.name == param.name }!! as KProperty1<Any, Any?> + ) + }.associateBy { it.name } + val jsonElementAdapter = gson.getAdapter(JsonElement::class.java) + + return object : TypeAdapter<T>() { + override fun write(out: JsonWriter, value: T?) { + if (value == null) { + out.nullValue() + return + } + out.beginObject() + parameterInfos.forEach { (name, paramInfo) -> + out.name(name) + paramInfo.adapter.write(out, paramInfo.field.get(value)) + } + if (extraDataParam != null) { + val extraData = extraDataParam.second.get(value) + extraData.forEach { (extraName, extraValue) -> + out.name(extraName) + jsonElementAdapter.write(out, extraValue) + } + } + out.endObject() + } + + override fun read(reader: JsonReader): T? { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull() + return null + } + reader.beginObject() + val args = mutableMapOf<KParameter, Any?>() + val extraData = mutableMapOf<String, JsonElement>() + while (reader.peek() != JsonToken.END_OBJECT) { + val name = reader.nextName() + val paramData = parameterInfos[name] + if (paramData == null) { + extraData[name] = jsonElementAdapter.read(reader) + continue + } + val value = paramData.adapter.read(reader) + args[paramData.param] = value + } + reader.endObject() + if (extraDataParam != null) { + args[extraDataParam.first] = extraData + } + return primaryConstructor.callBy(args) + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/NBTTypeAdapter.kt b/src/main/java/at/hannibal2/skyhanni/utils/NBTTypeAdapter.kt new file mode 100644 index 000000000..607fdfc2c --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/NBTTypeAdapter.kt @@ -0,0 +1,23 @@ +package at.hannibal2.skyhanni.utils + +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import net.minecraft.nbt.CompressedStreamTools +import net.minecraft.nbt.NBTTagCompound +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.util.* + +object NBTTypeAdapter : TypeAdapter<NBTTagCompound>() { + override fun write(out: JsonWriter, value: NBTTagCompound) { + val baos = ByteArrayOutputStream() + CompressedStreamTools.writeCompressed(value, baos) + out.value(Base64.getEncoder().encode(baos.toByteArray()).decodeToString()) + } + + override fun read(reader: JsonReader): NBTTagCompound { + val bais = ByteArrayInputStream(Base64.getDecoder().decode(reader.nextString())) + return CompressedStreamTools.readCompressed(bais) + } +}
\ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt index af0f26bcc..f44893c2a 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt @@ -154,7 +154,7 @@ object SkyBlockItemModifierUtils { // TODO untested fun ItemStack.hasBookOfStats() = getAttributeBoolean("stats_book") - fun ItemStack.hasArtOfPiece() = getAttributeBoolean("artOfPeaceApplied") + fun ItemStack.hasArtOfPeace() = getAttributeBoolean("artOfPeaceApplied") fun ItemStack.getLivingMetalProgress() = getAttributeInt("lm_evo") diff --git a/src/test/java/at/hannibal2/skyhanni/test/BootstrapHook.kt b/src/test/java/at/hannibal2/skyhanni/test/BootstrapHook.kt new file mode 100644 index 000000000..3aff72c2b --- /dev/null +++ b/src/test/java/at/hannibal2/skyhanni/test/BootstrapHook.kt @@ -0,0 +1,30 @@ +package at.hannibal2.skyhanni.test + +import at.hannibal2.skyhanni.utils.LorenzUtils.makeAccessible +import net.minecraft.block.Block +import net.minecraft.block.BlockFire +import net.minecraft.init.Bootstrap +import net.minecraft.item.Item +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.Extension +import org.junit.jupiter.api.extension.ExtensionContext +import java.util.concurrent.locks.Lock +import java.util.concurrent.locks.ReentrantLock + +class BootstrapHook : BeforeAllCallback, Extension { + companion object { + private val LOCK: Lock = ReentrantLock() + } + + override fun beforeAll(p0: ExtensionContext?) { + LOCK.lock() + try { + Bootstrap::class.java.getDeclaredField("alreadyRegistered").makeAccessible().set(null, true) + Block.registerBlocks() + BlockFire.init() + Item.registerItems() + } finally { + LOCK.unlock() + } + } +}
\ No newline at end of file diff --git a/src/test/java/at/hannibal2/skyhanni/test/ItemModifierTest.kt b/src/test/java/at/hannibal2/skyhanni/test/ItemModifierTest.kt new file mode 100644 index 000000000..0fc172230 --- /dev/null +++ b/src/test/java/at/hannibal2/skyhanni/test/ItemModifierTest.kt @@ -0,0 +1,28 @@ +package at.hannibal2.skyhanni.test + +import at.hannibal2.skyhanni.utils.ItemUtils.isEnchanted +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getDungeonStarCount +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getEnchantments +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getHotPotatoCount +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getItemUuid +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.getReforgeName +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.hasArtOfPeace +import at.hannibal2.skyhanni.utils.SkyBlockItemModifierUtils.isRecombobulated +import org.junit.jupiter.api.Test + +class ItemModifierTest { + + + @Test + fun testUpgradeLevelMasterStars() { + val itemStack = TestExportTools.getTestData(TestExportTools.Item, "10starnecronhead") + assert(!itemStack.isRecombobulated()) + assert(itemStack.getReforgeName() == "ancient") + assert(itemStack.getItemUuid() == "2810b7fe-33af-4dab-bb41-b4815f5847af") + assert(itemStack.isEnchanted()) + assert(itemStack.getHotPotatoCount() == 15) + assert(itemStack.getEnchantments()?.size == 11) + assert(itemStack.hasArtOfPeace()) + assert(itemStack.getDungeonStarCount() == 10) + } +}
\ No newline at end of file diff --git a/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 000000000..5b7b6dd31 --- /dev/null +++ b/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +at.hannibal2.skyhanni.test.BootstrapHook diff --git a/src/test/resources/testdata/Item/10starnecronhead.json b/src/test/resources/testdata/Item/10starnecronhead.json new file mode 100644 index 000000000..1e5bbe7b0 --- /dev/null +++ b/src/test/resources/testdata/Item/10starnecronhead.json @@ -0,0 +1 @@ +{"type":"Item","data":"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\u003d\u003d"}
\ No newline at end of file |