From f4a9e4011b09be043ec086abd365d0e8c443bbec Mon Sep 17 00:00:00 2001 From: Roman / Linnea Gräf Date: Wed, 31 May 2023 13:04:41 +0200 Subject: Add NPC Sell price to tooltip (#702) --- .../util/hypixelapi/HypixelItemAPI.kt | 58 ++++++++++ .../util/kotlin/KotlinTypeAdapterFactory.kt | 128 +++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 src/main/kotlin/io/github/moulberry/notenoughupdates/util/hypixelapi/HypixelItemAPI.kt create mode 100644 src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/KotlinTypeAdapterFactory.kt (limited to 'src/main/kotlin') diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/hypixelapi/HypixelItemAPI.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/hypixelapi/HypixelItemAPI.kt new file mode 100644 index 00000000..acb2b53f --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/hypixelapi/HypixelItemAPI.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see . + */ + +package io.github.moulberry.notenoughupdates.util.hypixelapi + +import com.google.gson.JsonElement +import com.google.gson.annotations.SerializedName +import io.github.moulberry.notenoughupdates.NotEnoughUpdates +import io.github.moulberry.notenoughupdates.util.kotlin.ExtraData +import io.github.moulberry.notenoughupdates.util.kotlin.KSerializable +import io.github.moulberry.notenoughupdates.util.kotlin.KSerializedName + +object HypixelItemAPI { + @KSerializable + data class ApiResponse( + val items: List, + ) + + @KSerializable + data class ApiItem( + val id: String, + val name: String, + @param:KSerializedName("npc_sell_price") + val npcSellPrice: Double? = null, + @param:ExtraData + val extraData: Map, + ) + + var itemData: Map = mapOf() + private set + + @JvmStatic + fun getNPCSellPrice(itemId: String) = itemData[itemId]?.npcSellPrice + + fun loadItemData() { + NotEnoughUpdates.INSTANCE.manager.apiUtils.newAnonymousHypixelApiRequest("resources/skyblock/items") + .requestJson(ApiResponse::class.java) + .thenAccept { + itemData = it.items.associateBy { it.id } + } + } +} diff --git a/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/KotlinTypeAdapterFactory.kt b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/KotlinTypeAdapterFactory.kt new file mode 100644 index 00000000..e531364a --- /dev/null +++ b/src/main/kotlin/io/github/moulberry/notenoughupdates/util/kotlin/KotlinTypeAdapterFactory.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see . + */ + +package io.github.moulberry.notenoughupdates.util.kotlin + +import com.google.gson.* +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.VALUE_PARAMETER) +annotation class ExtraData + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.VALUE_PARAMETER) +annotation class KSerializedName(val serialName: String) + +object KotlinTypeAdapterFactory : TypeAdapterFactory { + + internal data class ParameterInfo( + val param: KParameter, + val adapter: TypeAdapter, + val name: String, + val field: KProperty1 + ) + + @OptIn(ExperimentalStdlibApi::class) + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + val kotlinClass = type.rawType.kotlin as KClass + if (kotlinClass.findAnnotation() == null) return null + if (!kotlinClass.isData) return null + val primaryConstructor = kotlinClass.primaryConstructor ?: return null + val params = primaryConstructor.parameters.filter { it.findAnnotation() == null } + val extraDataParam = primaryConstructor.parameters + .find { it.findAnnotation() != null && typeOf>().isSubtypeOf(it.type) } + ?.let { param -> + param to kotlinClass.memberProperties.find { it.name == param.name && it.returnType.isSubtypeOf(typeOf>()) } as KProperty1> + } + val parameterInfos = params.map { param -> + ParameterInfo( + param, + gson.getAdapter( + TypeToken.get(InternalGsonTypes.resolve(type.type, type.rawType, param.type.javaType)) + ) as TypeAdapter, + param.findAnnotation()?.serialName ?: param.name!!, + kotlinClass.memberProperties.find { it.name == param.name }!! as KProperty1 + ) + }.associateBy { it.name } + val jsonElementAdapter = gson.getAdapter(JsonElement::class.java) + + return object : TypeAdapter() { + 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() + val extraData = mutableMapOf() + 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) + } + } + } +} + -- cgit