Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.spongepowered.asm.mixin.gen.Invoker;

@Mixin(BeaconBlockEntityRenderer.class)
public interface BeaconBlockEntityRendererInvoker {
public interface BeaconBlockEntityRendererAccessor {
@Invoker
static void invokeRenderBeam(
MatrixStack matrices,
Expand Down
137 changes: 11 additions & 126 deletions src/main/kotlin/me/nobaboy/nobaaddons/api/InventoryAPI.kt
Original file line number Diff line number Diff line change
@@ -1,67 +1,23 @@
package me.nobaboy.nobaaddons.api

//? if >=1.21.5 {
/*import me.nobaboy.nobaaddons.mixins.accessors.PlayerInventoryAccessor
*///?}

import me.nobaboy.nobaaddons.api.skyblock.SkyBlockAPI
import me.nobaboy.nobaaddons.config.NobaConfig
import me.nobaboy.nobaaddons.data.InventoryData
import me.nobaboy.nobaaddons.events.impl.client.InventoryEvents
import me.nobaboy.nobaaddons.events.impl.client.PacketEvents
import me.nobaboy.nobaaddons.events.impl.client.TickEvents
import me.nobaboy.nobaaddons.events.impl.client.WorldEvents
import me.nobaboy.nobaaddons.utils.MCUtils
import me.nobaboy.nobaaddons.utils.TextUtils.buildLiteral
import me.nobaboy.nobaaddons.utils.Timestamp
import net.minecraft.client.gui.screen.ChatScreen
import net.minecraft.entity.player.PlayerInventory
import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket
import net.minecraft.network.packet.s2c.play.CloseScreenS2CPacket
import net.minecraft.network.packet.s2c.play.InventoryS2CPacket
import net.minecraft.network.packet.s2c.play.OpenScreenS2CPacket
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket
import net.minecraft.text.Text
import net.minecraft.util.Formatting
import java.util.concurrent.ConcurrentHashMap

object InventoryAPI {
private val MERCHANT_COUNT = Regex("x\\d+")
private const val SKYBLOCK_MENU_SLOT = 8

private var currentScreen: Screen? = null
private var currentInventory: InventoryData? = null
private var currentWindow: Window? = null

private var inventorySuppressTime = Timestamp.distantPast()
private var previousItemCounts: Map<Text, Int>? = null
val itemLog = ConcurrentHashMap<Text, ItemDiff>()

private fun shouldSuppressItemLogUpdate(): Boolean = inventorySuppressTime.elapsedSeconds() < 2

fun init() {
TickEvents.every(5, this::onQuarterSecond)
PacketEvents.SEND.register(this::onPacketSend)
PacketEvents.POST_RECEIVE.register(this::onPacketReceive)
WorldEvents.LOAD.register { debounceItemLog() }
}

private fun onQuarterSecond(event: TickEvents.Tick) {
val player = event.client.player
if(!SkyBlockAPI.inSkyBlock || player == null) {
previousItemCounts = null
itemLog.clear()
return
}

if(event.client.currentScreen == null) {
val current = player.inventory.itemNamesToCount()
previousItemCounts?.takeIf { !shouldSuppressItemLogUpdate() }?.let { updateItemLog(it, current) }
previousItemCounts = current
}

itemLog.entries.removeIf { (_, diff) ->
diff.timestamp.elapsedSeconds() > NobaConfig.inventory.itemPickupLog.timeoutSeconds
}
}

private fun onPacketSend(event: PacketEvents.Send) {
Expand All @@ -80,11 +36,11 @@ object InventoryAPI {
}

private fun onScreenOpen(packet: OpenScreenS2CPacket) {
currentWindow = Window(packet.syncId, packet.name.string)
currentScreen = Screen(packet.syncId, packet.name.string)
}

private fun onInventory(packet: InventoryS2CPacket) {
if(packet.syncId != currentWindow?.id) return
if(packet.syncId != currentScreen?.id) return

val slotCount = packet.contents.size - 36
val items = packet.contents
Expand All @@ -94,101 +50,30 @@ object InventoryAPI {
.associate { it.index to it.value }
.toMutableMap()

currentInventory = InventoryData(currentWindow!!.id, currentWindow!!.title, slotCount, items).also(::ready)
currentInventory = InventoryData(currentScreen!!.id, currentScreen!!.title, slotCount, items).also(::ready)
}

private fun onSlotUpdate(packet: ScreenHandlerSlotUpdateS2CPacket) {
if(packet.syncId != currentWindow?.id) {
InventoryEvents.SLOT_UPDATE.dispatch(InventoryEvents.SlotUpdate(packet.stack, packet.slot))
return
}
if(packet.syncId != currentScreen?.id) return

val inventory = currentInventory ?: return
val currentInventory = currentInventory ?: return
val slot = packet.slot

if(slot >= inventory.slotCount) return
packet.stack?.let { inventory.items[slot] = it }
if(slot >= currentInventory.slotCount) return
currentInventory.items[slot] = packet.stack

InventoryEvents.UPDATE.dispatch(InventoryEvents.Update(inventory))
InventoryEvents.UPDATE.dispatch(InventoryEvents.Update(currentInventory))
}

private fun ready(inventory: InventoryData) {
InventoryEvents.OPEN.dispatch(InventoryEvents.Open(inventory))
InventoryEvents.UPDATE.dispatch(InventoryEvents.Update(inventory))
}

private fun debounceItemLog() {
previousItemCounts = null
inventorySuppressTime = Timestamp.now()
}

private fun close(sameName: Boolean = false) {
if(MCUtils.client.currentScreen is ChatScreen) return
InventoryEvents.CLOSE.dispatch(InventoryEvents.Close(sameName))
}

private fun updateItemLog(previous: Map<Text, Int>, current: Map<Text, Int>) {
// this MUST be annotated, as the kotlin compiler can't figure out the typing automatically from getOrPut,
// despite the fact that intellij doesn't throw any warnings.
val diffs = buildMap<Text, ItemDiff> {
for(item in (previous.keys + current.keys)) {
val previousCount = previous[item] ?: 0
val currentCount = current[item] ?: 0
if(previousCount == currentCount) continue
getOrPut(item) { ItemDiff(item) }.change += currentCount - previousCount
}
}

for((name, diff) in diffs) {
if(diff.change == 0) continue
val logDiff = itemLog.getOrPut(name) { ItemDiff(name) }
logDiff.change += diff.change
logDiff.timestamp = Timestamp.now()
}
}

private fun Text.removeMerchantCount(): Text {
if(siblings.size <= 1) return this
val last = siblings.last()
// celeste — Today at 01:19
// i have reworded this comment like 10 times now
// i cannot figure out a way to properly express how much i hate having to do this
if(last.string.matches(MERCHANT_COUNT) && last.style.color?.rgb == Formatting.DARK_GRAY.colorValue!!) {
val copy = copy()
copy.siblings.removeLast()
val name = copy.siblings.removeLast()
val content = name.string.removeSuffix(" ")
copy.append(buildLiteral(content) { style = name.style })
return copy
}
return this
}

private fun PlayerInventory.itemNamesToCount(): Map<Text, Int> = buildMap {
//? if >=1.21.5 {
/*val main = (this@itemNamesToCount as PlayerInventoryAccessor).main
*///?}

for(slot in 0 until main.size) {
if(slot == SKYBLOCK_MENU_SLOT) {
continue
}

val item = main[slot]
if(item.isEmpty) continue
val name = item.name.removeMerchantCount()

merge(name, item.count, Int::plus)
}
// TODO fix for 1.21.5
// offHand.firstOrNull()?.let { merge(name, it.count, Int::plus) }
}

data class Window(val id: Int, val title: String)

data class ItemDiff(
val name: Text,
var change: Int = 0,
var timestamp: Timestamp = Timestamp.now(),
)
}
data class Screen(val id: Int, val title: String)
}
6 changes: 2 additions & 4 deletions src/main/kotlin/me/nobaboy/nobaaddons/api/skyblock/PetAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ object PetAPI {

event.inventory.items.values.forEach { itemStack ->
val pet = getPetData(itemStack) ?: return@forEach
if(!pet.active) return@forEach

changePet(pet)
if(pet.active) changePet(pet)
}
}

Expand All @@ -65,7 +63,7 @@ object PetAPI {
if(event.button != GLFW.GLFW_MOUSE_BUTTON_1) return
if(event.actionType != SlotActionType.PICKUP) return

getPetData(event.itemStack)?.let { changePet(it) }
getPetData(event.stack)?.let { changePet(it) }
}

private fun onChatMessage(event: ChatMessageEvents.Chat) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,11 @@ object SkyBlockAPI {

private fun onLocationPacket(packet: ClientboundLocationPacket) {
currentServer = packet.serverType.getOrNull()
currentIsland = SkyBlockIsland.getByName(packet.mode.getOrNull() ?: return)
if(currentIsland != SkyBlockIsland.UNKNOWN) Scheduler.schedule(2) {
SkyBlockEvents.ISLAND_CHANGE.dispatch(SkyBlockEvents.IslandChange(currentIsland))
val newIsland = packet.mode.map(SkyBlockIsland::getByName).orElse(SkyBlockIsland.UNKNOWN)

if(newIsland != currentIsland) Scheduler.schedule(2) {
SkyBlockEvents.ISLAND_CHANGE.dispatch(SkyBlockEvents.IslandChange(newIsland))
currentIsland = newIsland
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object HoppityAPI {
val inRelevantIsland: Boolean get() = unsupportedIslands.none { it.inIsland() }

val hasLocatorInHand: Boolean get() = MCUtils.player?.mainHandStack?.skyBlockId == LOCATOR
val hasLocatorInHotbar: Boolean get() = InventoryUtils.getItemsInHotbar().any { it.skyBlockId == LOCATOR }
val hasLocatorInHotbar: Boolean get() = InventoryUtils.getHotbarItems().any { it.skyBlockId == LOCATOR }

private val unsupportedIslands = listOf(
SkyBlockIsland.PRIVATE_ISLAND,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ object DianaAPI {
val isActive: Boolean get() = SkyBlockIsland.HUB.inIsland() && isRitualActive && hasSpadeInHotbar
private val isRitualActive: Boolean get() = MayorPerk.MYTHOLOGICAL_RITUAL.isActive()

private val hasSpadeInHotbar: Boolean get() = InventoryUtils.getItemsInHotbar().any { it.skyBlockId == SPADE }
private val hasSpadeInHotbar: Boolean get() = InventoryUtils.getHotbarItems().any { it.skyBlockId == SPADE }
fun hasSpadeInHand(player: PlayerEntity): Boolean = player.mainHandStack.skyBlockId == SPADE
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ object InventoryCategory {
name = CommonText.Config.ENABLED
booleanController()
}
add({ inventory.itemPickupLog::compactLines }) {
name = tr("nobaaddons.config.inventory.itemPickupLog.compactLines", "Compact Lines")
require { option(enabled) }
booleanController()
}
add({ inventory.itemPickupLog::timeoutSeconds }) {
name = tr("nobaaddons.config.inventory.itemPickupLog.timeout", "Expire After")
require { option(enabled) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class InventoryConfig {

class ItemPickupLog {
var enabled = false
var compactLines = false
var timeoutSeconds = 5
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ object InventoryEvents {
data class Open(val inventory: InventoryData) : Event
data class Close(val sameName: Boolean) : Event
data class Update(val inventory: InventoryData) : Event
data class SlotUpdate(val itemStack: ItemStack, val slot: Int) : Event
data class SlotClick(val itemStack: ItemStack, val button: Int, val slot: Int, val actionType: SlotActionType) : Event
data class SlotUpdate(val stack: ItemStack, val slot: Int) : Event
data class SlotClick(val stack: ItemStack, val button: Int, val slot: Int, val actionType: SlotActionType) : Event
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package me.nobaboy.nobaaddons.features.events.mythological

import me.nobaboy.nobaaddons.api.skyblock.events.mythological.DianaAPI
import me.nobaboy.nobaaddons.config.NobaConfig
import me.nobaboy.nobaaddons.events.impl.client.InventoryEvents
import me.nobaboy.nobaaddons.utils.TextUtils.buildText
import me.nobaboy.nobaaddons.utils.TimedSet
Expand All @@ -11,6 +13,9 @@ import net.minecraft.util.Formatting
import kotlin.time.Duration.Companion.seconds

object AnnounceRareDrops {
private val config get() = NobaConfig.events.mythological
private val enabled: Boolean get() = config.announceRareDrops && DianaAPI.isActive

private val uuidCache = TimedSet<String>(10.seconds)

private val rareDrops = listOf(
Expand All @@ -25,8 +30,10 @@ object AnnounceRareDrops {
}

private fun onSlotUpdate(event: InventoryEvents.SlotUpdate) {
val itemStack = event.itemStack
val item = itemStack.asSkyBlockItem ?: return
if(!enabled) return

val stack = event.stack
val item = stack.asSkyBlockItem ?: return

if(item.id !in rareDrops) return
item.timestamp?.let { if(it.elapsedSince() > 3.seconds) return } ?: return
Expand All @@ -38,7 +45,7 @@ object AnnounceRareDrops {
val text = buildText {
append(tr("nobaaddons.chat.rareDrop", "RARE DROP!").formatted(Formatting.GOLD, Formatting.BOLD))
append(" ")
append(itemStack.name)
append(stack.name)
}

ChatUtils.addMessage(text, prefix = false)
Expand Down
Loading
Loading