aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authornea <nea@nea.moe>2023-08-06 23:03:06 +0200
committerhannibal2 <24389977+hannibal002@users.noreply.github.com>2023-08-07 00:53:31 +0200
commit1da07465b60a8e357373d7c3a3f764dfa4d5960b (patch)
treebc94e3caa665223ef273e6166c3d9746af23970a /src
parentfa464f1d1eef4e1d320c47350fe6ded16b36b3ed (diff)
downloadskyhanni-1da07465b60a8e357373d7c3a3f764dfa4d5960b.tar.gz
skyhanni-1da07465b60a8e357373d7c3a3f764dfa4d5960b.tar.bz2
skyhanni-1da07465b60a8e357373d7c3a3f764dfa4d5960b.zip
Basic test for dungeon items
Diffstat (limited to 'src')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/SkyHanniMod.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/config/features/DevConfig.java6
-rw-r--r--src/main/java/at/hannibal2/skyhanni/features/misc/items/EstimatedItemValue.kt4
-rw-r--r--src/main/java/at/hannibal2/skyhanni/test/TestExportTools.kt59
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ItemStackTypeAdapter.kt29
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/ItemUtils.kt2
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/JsonUtils.kt11
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/KSerializable.kt109
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/NBTTypeAdapter.kt23
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/SkyBlockItemModifierUtils.kt2
-rw-r--r--src/test/java/at/hannibal2/skyhanni/test/BootstrapHook.kt30
-rw-r--r--src/test/java/at/hannibal2/skyhanni/test/ItemModifierTest.kt28
-rw-r--r--src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension1
-rw-r--r--src/test/resources/testdata/Item/10starnecronhead.json1
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