feat: Add bazaar/ah search hotkey

This commit is contained in:
Linnea Gräf
2024-11-17 17:29:15 +01:00
parent 915ab96b24
commit 970dfddaf9
11 changed files with 196 additions and 78 deletions

View File

@@ -35,6 +35,7 @@ import kotlin.coroutines.EmptyCoroutineContext
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
import moe.nea.firmament.events.ClientInitEvent
import moe.nea.firmament.events.ClientStartedEvent import moe.nea.firmament.events.ClientStartedEvent
import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.ItemTooltipEvent import moe.nea.firmament.events.ItemTooltipEvent
@@ -141,6 +142,7 @@ object Firmament {
ScreenRenderPostEvent.publish(ScreenRenderPostEvent(screen, mouseX, mouseY, tickDelta, drawContext)) ScreenRenderPostEvent.publish(ScreenRenderPostEvent(screen, mouseX, mouseY, tickDelta, drawContext))
}) })
}) })
ClientInitEvent.publish(ClientInitEvent())
} }

View File

@@ -0,0 +1,5 @@
package moe.nea.firmament.events
class ClientInitEvent : FirmamentEvent() {
companion object : FirmamentEventBus<ClientInitEvent>()
}

View File

@@ -90,6 +90,9 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
fun subscribeEvents() { fun subscribeEvents() {
SubscriptionList.allLists.forEach { SubscriptionList.allLists.forEach {
it.provideSubscriptions { it.provideSubscriptions {
it.owner.javaClass.classes.forEach {
runCatching { it.getDeclaredField("INSTANCE").get(null) }
}
subscribeSingleEvent(it) subscribeSingleEvent(it)
} }
} }

View File

@@ -0,0 +1,39 @@
package moe.nea.firmament.features.inventory
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.repo.HypixelStaticData
import moe.nea.firmament.repo.ItemCache
import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.repo.ItemCache.isBroken
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.focusedItemStack
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.skyblock.SBItemUtil.getSearchName
object ItemHotkeys {
object TConfig : ManagedConfig("item-hotkeys", Category.INVENTORY) {
val openGlobalTradeInterface by keyBindingWithDefaultUnbound("global-trade-interface")
}
@Subscribe
fun onHandledInventoryPress(event: HandledScreenKeyPressedEvent) {
if (!event.matches(TConfig.openGlobalTradeInterface)) {
return
}
var item = event.screen.focusedItemStack ?: return
val skyblockId = item.skyBlockId ?: return
item = RepoManager.getNEUItem(skyblockId)?.asItemStack()?.takeIf { !it.isBroken } ?: item
if (HypixelStaticData.hasBazaarStock(skyblockId)) {
MC.sendCommand("bz ${item.getSearchName()}")
} else if (HypixelStaticData.hasAuctionHouseOffers(skyblockId)) {
MC.sendCommand("ahs ${item.getSearchName()}")
} else {
return
}
event.cancel()
}
}

View File

@@ -34,9 +34,6 @@ import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch import moe.nea.firmament.util.useMatch
object HotmPresets { object HotmPresets {
object Config : ManagedConfig("hotm-presets", Category.MINING) {
}
val SHARE_PREFIX = "FIRMHOTM/" val SHARE_PREFIX = "FIRMHOTM/"
@Serializable @Serializable

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.repo package moe.nea.firmament.repo
import io.ktor.client.call.body import io.ktor.client.call.body
@@ -21,87 +19,93 @@ import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.async.waitForInput import moe.nea.firmament.util.async.waitForInput
object HypixelStaticData { object HypixelStaticData {
private val logger = LogManager.getLogger("Firmament.HypixelStaticData") private val logger = LogManager.getLogger("Firmament.HypixelStaticData")
private val moulberryBaseUrl = "https://moulberry.codes" private val moulberryBaseUrl = "https://moulberry.codes"
private val hypixelApiBaseUrl = "https://api.hypixel.net" private val hypixelApiBaseUrl = "https://api.hypixel.net"
var lowestBin: Map<SkyblockId, Double> = mapOf() var lowestBin: Map<SkyblockId, Double> = mapOf()
private set private set
var bazaarData: Map<SkyblockId, BazaarData> = mapOf() var bazaarData: Map<SkyblockId, BazaarData> = mapOf()
private set private set
var collectionData: Map<String, CollectionSkillData> = mapOf() var collectionData: Map<String, CollectionSkillData> = mapOf()
private set private set
@Serializable @Serializable
data class BazaarData( data class BazaarData(
@SerialName("product_id") @SerialName("product_id")
val productId: SkyblockId.BazaarStock, val productId: SkyblockId.BazaarStock,
@SerialName("quick_status") @SerialName("quick_status")
val quickStatus: BazaarStatus, val quickStatus: BazaarStatus,
) )
@Serializable @Serializable
data class BazaarStatus( data class BazaarStatus(
val sellPrice: Double, val sellPrice: Double,
val sellVolume: Long, val sellVolume: Long,
val sellMovingWeek: Long, val sellMovingWeek: Long,
val sellOrders: Long, val sellOrders: Long,
val buyPrice: Double, val buyPrice: Double,
val buyVolume: Long, val buyVolume: Long,
val buyMovingWeek: Long, val buyMovingWeek: Long,
val buyOrders: Long val buyOrders: Long
) )
@Serializable @Serializable
private data class BazaarResponse( private data class BazaarResponse(
val success: Boolean, val success: Boolean,
val products: Map<SkyblockId.BazaarStock, BazaarData> = mapOf(), val products: Map<SkyblockId.BazaarStock, BazaarData> = mapOf(),
) )
fun getPriceOfItem(item: SkyblockId): Double? = bazaarData[item]?.quickStatus?.buyPrice ?: lowestBin[item] fun getPriceOfItem(item: SkyblockId): Double? = bazaarData[item]?.quickStatus?.buyPrice ?: lowestBin[item]
fun hasBazaarStock(item: SkyblockId): Boolean {
return item in bazaarData
}
fun spawnDataCollectionLoop() { fun hasAuctionHouseOffers(item: SkyblockId): Boolean {
Firmament.coroutineScope.launch { return (item in lowestBin) // TODO: || (item in biddableAuctionPrices)
logger.info("Updating collection data") }
updateCollectionData()
}
Firmament.coroutineScope.launch {
while (true) {
logger.info("Updating NEU prices")
updatePrices()
withTimeoutOrNull(10.minutes) { waitForInput(IKeyBinding.ofKeyCode(GLFW.GLFW_KEY_U)) }
}
}
}
private suspend fun updatePrices() { fun spawnDataCollectionLoop() {
awaitAll( Firmament.coroutineScope.launch {
Firmament.coroutineScope.async { fetchBazaarPrices() }, logger.info("Updating collection data")
Firmament.coroutineScope.async { fetchPricesFromMoulberry() }, updateCollectionData()
) }
} Firmament.coroutineScope.launch {
while (true) {
logger.info("Updating NEU prices")
updatePrices()
}
}
}
private suspend fun fetchPricesFromMoulberry() { private suspend fun updatePrices() {
lowestBin = Firmament.httpClient.get("$moulberryBaseUrl/lowestbin.json") awaitAll(
.body<Map<SkyblockId, Double>>() Firmament.coroutineScope.async { fetchBazaarPrices() },
} Firmament.coroutineScope.async { fetchPricesFromMoulberry() },
)
}
private suspend fun fetchBazaarPrices() { private suspend fun fetchPricesFromMoulberry() {
val response = Firmament.httpClient.get("$hypixelApiBaseUrl/skyblock/bazaar").body<BazaarResponse>() lowestBin = Firmament.httpClient.get("$moulberryBaseUrl/lowestbin.json")
if (!response.success) { .body<Map<SkyblockId, Double>>()
logger.warn("Retrieved unsuccessful bazaar data") }
}
bazaarData = response.products.mapKeys { it.key.toRepoId() }
}
private suspend fun updateCollectionData() { private suspend fun fetchBazaarPrices() {
val response = val response = Firmament.httpClient.get("$hypixelApiBaseUrl/skyblock/bazaar").body<BazaarResponse>()
Firmament.httpClient.get("$hypixelApiBaseUrl/resources/skyblock/collections").body<CollectionResponse>() if (!response.success) {
if (!response.success) { logger.warn("Retrieved unsuccessful bazaar data")
logger.warn("Retrieved unsuccessful collection data") }
} bazaarData = response.products.mapKeys { it.key.toRepoId() }
collectionData = response.collections }
logger.info("Downloaded ${collectionData.values.sumOf { it.items.values.size }} collections")
} private suspend fun updateCollectionData() {
val response =
Firmament.httpClient.get("$hypixelApiBaseUrl/resources/skyblock/collections").body<CollectionResponse>()
if (!response.success) {
logger.warn("Retrieved unsuccessful collection data")
}
collectionData = response.collections
logger.info("Downloaded ${collectionData.values.sumOf { it.items.values.size }} collections")
}
} }

View File

@@ -23,6 +23,7 @@ import net.minecraft.item.Items
import net.minecraft.nbt.NbtCompound import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtOps import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtString
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.Firmament import moe.nea.firmament.Firmament
import moe.nea.firmament.gui.config.HudMeta import moe.nea.firmament.gui.config.HudMeta
@@ -33,11 +34,11 @@ import moe.nea.firmament.util.LegacyTagParser
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.TestUtil import moe.nea.firmament.util.TestUtil
import moe.nea.firmament.util.mc.FirmamentDataComponentTypes
import moe.nea.firmament.util.mc.appendLore import moe.nea.firmament.util.mc.appendLore
import moe.nea.firmament.util.mc.modifyLore import moe.nea.firmament.util.mc.modifyLore
import moe.nea.firmament.util.mc.setCustomName import moe.nea.firmament.util.mc.setCustomName
import moe.nea.firmament.util.mc.setSkullOwner import moe.nea.firmament.util.mc.setSkullOwner
import moe.nea.firmament.util.skyblockId
object ItemCache : IReloadable { object ItemCache : IReloadable {
private val cache: MutableMap<String, ItemStack> = ConcurrentHashMap() private val cache: MutableMap<String, ItemStack> = ConcurrentHashMap()
@@ -67,6 +68,8 @@ object ItemCache : IReloadable {
null null
} }
val ItemStack.isBroken
get() = get(FirmamentDataComponentTypes.IS_BROKEN) ?: false
fun brokenItemStack(neuItem: NEUItem?, idHint: SkyblockId? = null): ItemStack { fun brokenItemStack(neuItem: NEUItem?, idHint: SkyblockId? = null): ItemStack {
return ItemStack(Items.PAINTING).apply { return ItemStack(Items.PAINTING).apply {
setCustomName(Text.literal(neuItem?.displayName ?: idHint?.neuItem ?: "null")) setCustomName(Text.literal(neuItem?.displayName ?: idHint?.neuItem ?: "null"))
@@ -78,6 +81,10 @@ object ItemCache : IReloadable {
) )
) )
) )
set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(NbtCompound().apply {
put("ID", NbtString.of(neuItem?.skyblockItemId ?: idHint?.neuItem ?: "null"))
}))
set(FirmamentDataComponentTypes.IS_BROKEN, true)
} }
} }

View File

@@ -49,6 +49,7 @@ object MC {
messageQueue.add(text) messageQueue.add(text)
} }
@Deprecated("Use checked method instead", replaceWith = ReplaceWith("sendCommand(command)"))
fun sendServerCommand(command: String) { fun sendServerCommand(command: String) {
val nh = player?.networkHandler ?: return val nh = player?.networkHandler ?: return
nh.sendPacket( nh.sendPacket(

View File

@@ -0,0 +1,36 @@
package moe.nea.firmament.util.mc
import com.mojang.serialization.Codec
import net.minecraft.component.ComponentType
import net.minecraft.registry.Registries
import net.minecraft.registry.Registry
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ClientInitEvent
object FirmamentDataComponentTypes {
@Subscribe
fun init(event: ClientInitEvent) {
}
private fun <T> register(
id: String,
builderOperator: (ComponentType.Builder<T>) -> Unit
): ComponentType<T> {
return Registry.register(
Registries.DATA_COMPONENT_TYPE,
Firmament.identifier(id),
ComponentType.builder<T>().also(builderOperator)
.build()
)
}
val IS_BROKEN = register<Boolean>(
"is_broken"
) {
it.codec(Codec.BOOL.fieldOf("is_broken").codec())
}
}

View File

@@ -0,0 +1,21 @@
package moe.nea.firmament.util.skyblock
import net.minecraft.item.ItemStack
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.unformattedString
object SBItemUtil {
fun ItemStack.getSearchName(): String {
val name = this.name.unformattedString
if (name.contains("Enchanted Book")) {
val enchant = loreAccordingToNbt.firstOrNull()?.unformattedString
if (enchant != null) return enchant
}
if (name.startsWith("[Lvl")) {
val closing = name.indexOf(']')
if (closing > 0)
return name.substring(closing)
}
return name
}
}

View File

@@ -119,6 +119,9 @@
"firmament.config.inventory-buttons": "Inventory buttons", "firmament.config.inventory-buttons": "Inventory buttons",
"firmament.config.inventory-buttons.open-editor": "Open Editor", "firmament.config.inventory-buttons.open-editor": "Open Editor",
"firmament.config.inventory-buttons.open-editor.description": "Click anywhere to create a new inventory button or to edit one. Hold SHIFT to grid align.", "firmament.config.inventory-buttons.open-editor.description": "Click anywhere to create a new inventory button or to edit one. Hold SHIFT to grid align.",
"firmament.config.item-hotkeys": "Item Hotkeys",
"firmament.config.item-hotkeys.global-trade-interface": "Search on Bazaar/AH",
"firmament.config.item-hotkeys.global-trade-interface.description": "Press this button to search the hovered item on the bazaar or auction house.",
"firmament.config.item-rarity-cosmetics": "Item Rarity Cosmetics", "firmament.config.item-rarity-cosmetics": "Item Rarity Cosmetics",
"firmament.config.item-rarity-cosmetics.background": "Slot Background Rarity", "firmament.config.item-rarity-cosmetics.background": "Slot Background Rarity",
"firmament.config.item-rarity-cosmetics.background-hotbar": "Hotbar Background Rarity", "firmament.config.item-rarity-cosmetics.background-hotbar": "Hotbar Background Rarity",