Add basic sack util

[no changelog]
This commit is contained in:
Linnea Gräf
2024-10-01 18:00:43 +02:00
parent beb14d73bd
commit a4eac70118
22 changed files with 350 additions and 177 deletions

View File

@@ -1,13 +1,12 @@
package moe.nea.firmament.mixins; package moe.nea.firmament.mixins;
import com.llamalad7.mixinextras.sugar.Local; import moe.nea.firmament.events.ChestInventoryUpdateEvent;
import moe.nea.firmament.events.PlayerInventoryUpdate; import moe.nea.firmament.events.PlayerInventoryUpdate;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientCommonNetworkHandler; import net.minecraft.client.network.ClientCommonNetworkHandler;
import net.minecraft.client.network.ClientConnectionState; import net.minecraft.client.network.ClientConnectionState;
import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.network.ClientConnection; import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.s2c.play.InventoryS2CPacket; import net.minecraft.network.packet.s2c.play.InventoryS2CPacket;
import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket;
@@ -18,36 +17,40 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPlayNetworkHandler.class) @Mixin(ClientPlayNetworkHandler.class)
public abstract class SlotUpdateListener extends ClientCommonNetworkHandler { public abstract class SlotUpdateListener extends ClientCommonNetworkHandler {
protected SlotUpdateListener(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) { protected SlotUpdateListener(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) {
super(client, connection, connectionState); super(client, connection, connectionState);
} }
@Inject( @Inject(
method = "onScreenHandlerSlotUpdate", method = "onScreenHandlerSlotUpdate",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/tutorial/TutorialManager;onSlotUpdate(Lnet/minecraft/item/ItemStack;)V")) at = @At(value = "INVOKE", target = "Lnet/minecraft/client/tutorial/TutorialManager;onSlotUpdate(Lnet/minecraft/item/ItemStack;)V"))
private void onSingleSlotUpdate( private void onSingleSlotUpdate(
ScreenHandlerSlotUpdateS2CPacket packet, ScreenHandlerSlotUpdateS2CPacket packet,
CallbackInfo ci) { CallbackInfo ci) {
var player = this.client.player; var player = this.client.player;
assert player != null; assert player != null;
if (packet.getSyncId() == ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID if (packet.getSyncId() == ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID
|| packet.getSyncId() == 0) { || packet.getSyncId() == 0) {
PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Single(packet.getSlot(), packet.getStack())); PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Single(packet.getSlot(), packet.getStack()));
} else if (packet.getSyncId() == player.currentScreenHandler.syncId) { } else if (packet.getSyncId() == player.currentScreenHandler.syncId) {
// TODO: dispatch single chest slot ChestInventoryUpdateEvent.Companion.publish(
} new ChestInventoryUpdateEvent.Single(packet.getSlot(), packet.getStack())
} );
}
}
@Inject(method = "onInventory", @Inject(method = "onInventory",
at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V",
shift = At.Shift.AFTER)) shift = At.Shift.AFTER))
private void onMultiSlotUpdate(InventoryS2CPacket packet, CallbackInfo ci) { private void onMultiSlotUpdate(InventoryS2CPacket packet, CallbackInfo ci) {
var player = this.client.player; var player = this.client.player;
assert player != null; assert player != null;
if (packet.getSyncId() == 0) { if (packet.getSyncId() == 0) {
PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Multi(packet.getContents())); PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Multi(packet.getContents()));
} else if (packet.getSyncId() == player.currentScreenHandler.syncId) { } else if (packet.getSyncId() == player.currentScreenHandler.syncId) {
// TODO: dispatch multi chest ChestInventoryUpdateEvent.Companion.publish(
} new ChestInventoryUpdateEvent.Multi(packet.getContents())
} );
}
}
} }

View File

@@ -0,0 +1,14 @@
package moe.nea.firmament.mixins.accessor;
import net.minecraft.client.gui.hud.ChatHud;
import net.minecraft.client.gui.hud.ChatHudLine;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(ChatHud.class)
public interface AccessorChatHud {
@Accessor("messages")
List<ChatHudLine> getMessages_firmament();
}

View File

@@ -4,7 +4,9 @@ import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType.string import com.mojang.brigadier.arguments.StringArgumentType.string
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import net.minecraft.nbt.NbtOps
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.text.TextCodecs
import moe.nea.firmament.apis.UrsaManager import moe.nea.firmament.apis.UrsaManager
import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.FirmamentEventBus import moe.nea.firmament.events.FirmamentEventBus
@@ -24,6 +26,7 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.ScreenUtil import moe.nea.firmament.util.ScreenUtil
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.accessors.messages
import moe.nea.firmament.util.collections.InstanceList import moe.nea.firmament.util.collections.InstanceList
import moe.nea.firmament.util.collections.WeakCache import moe.nea.firmament.util.collections.WeakCache
@@ -205,6 +208,14 @@ fun firmamentCommand() = literal("firmament") {
} }
} }
} }
thenLiteral("dumpchat") {
thenExecute {
MC.inGameHud.chatHud.messages.forEach {
val nbt = TextCodecs.CODEC.encodeStart(NbtOps.INSTANCE, it.content).orThrow
println(nbt)
}
}
}
thenLiteral("sbdata") { thenLiteral("sbdata") {
thenExecute { thenExecute {
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.profile", SBData.profileId)) source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.profile", SBData.profileId))

View File

@@ -0,0 +1,11 @@
package moe.nea.firmament.events
import net.minecraft.item.ItemStack
import moe.nea.firmament.util.MC
sealed class ChestInventoryUpdateEvent : FirmamentEvent() {
companion object : FirmamentEventBus<ChestInventoryUpdateEvent>()
data class Single(val slot: Int, val stack: ItemStack) : ChestInventoryUpdateEvent()
data class Multi(val contents: List<ItemStack>) : ChestInventoryUpdateEvent()
val inventory = MC.screen
}

View File

@@ -27,7 +27,7 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.MoulConfigUtils import moe.nea.firmament.util.MoulConfigUtils
import moe.nea.firmament.util.ScreenUtil import moe.nea.firmament.util.ScreenUtil
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.item.createSkullItem import moe.nea.firmament.util.mc.createSkullItem
import moe.nea.firmament.util.render.RenderInWorldContext import moe.nea.firmament.util.render.RenderInWorldContext
import moe.nea.firmament.util.setSkyBlockFirmamentUiId import moe.nea.firmament.util.setSkyBlockFirmamentUiId
import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.skyBlockId

View File

@@ -13,7 +13,7 @@ import moe.nea.firmament.events.SlotRenderEvents
import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.item.loreAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.collections.lastNotNullOfOrNull import moe.nea.firmament.util.collections.lastNotNullOfOrNull
import moe.nea.firmament.util.collections.memoizeIdentity import moe.nea.firmament.util.collections.memoizeIdentity
import moe.nea.firmament.util.unformattedString import moe.nea.firmament.util.unformattedString

View File

@@ -28,8 +28,8 @@ import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData import moe.nea.firmament.util.SBData
import moe.nea.firmament.util.SkyBlockIsland import moe.nea.firmament.util.SkyBlockIsland
import moe.nea.firmament.util.data.ProfileSpecificDataHolder import moe.nea.firmament.util.data.ProfileSpecificDataHolder
import moe.nea.firmament.util.item.displayNameAccordingToNbt import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.item.loreAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.json.DashlessUUIDSerializer import moe.nea.firmament.util.json.DashlessUUIDSerializer
import moe.nea.firmament.util.skyblockUUID import moe.nea.firmament.util.skyblockUUID
import moe.nea.firmament.util.unformattedString import moe.nea.firmament.util.unformattedString

View File

@@ -21,8 +21,8 @@ import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
import moe.nea.firmament.util.TIME_PATTERN import moe.nea.firmament.util.TIME_PATTERN
import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.extraAttributes import moe.nea.firmament.util.extraAttributes
import moe.nea.firmament.util.item.displayNameAccordingToNbt import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.item.loreAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.parseShortNumber import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.parseTimePattern import moe.nea.firmament.util.parseTimePattern
import moe.nea.firmament.util.render.RenderCircleProgress import moe.nea.firmament.util.render.RenderCircleProgress

View File

@@ -19,7 +19,7 @@ import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.collections.WeakCache import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.item.decodeProfileTextureProperty import moe.nea.firmament.util.mc.decodeProfileTextureProperty
import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.skyBlockId
object CustomSkyBlockTextures : FirmamentFeature { object CustomSkyBlockTextures : FirmamentFeature {

View File

@@ -5,8 +5,8 @@ import com.google.gson.JsonElement
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtString import net.minecraft.nbt.NbtString
import moe.nea.firmament.util.item.displayNameAccordingToNbt import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.item.loreAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt
data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate { data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate {
override fun test(stack: ItemStack): Boolean { override fun test(stack: ItemStack): Boolean {

View File

@@ -3,7 +3,7 @@ package moe.nea.firmament.features.texturepack
import com.google.gson.JsonElement import com.google.gson.JsonElement
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import moe.nea.firmament.util.item.loreAccordingToNbt import moe.nea.firmament.util.mc.loreAccordingToNbt
class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate { class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate {
object Parser : FirmamentModelPredicateParser { object Parser : FirmamentModelPredicateParser {

View File

@@ -12,8 +12,8 @@ import net.minecraft.item.ItemStack
import net.minecraft.item.Items import net.minecraft.item.Items
import moe.nea.firmament.rei.SBItemStack import moe.nea.firmament.rei.SBItemStack
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.item.setEncodedSkullOwner import moe.nea.firmament.util.mc.setEncodedSkullOwner
import moe.nea.firmament.util.item.zeroUUID import moe.nea.firmament.util.mc.zeroUUID
object ModifyEquipment : EntityModifier { object ModifyEquipment : EntityModifier {
val names = mapOf( val names = mapOf(

View File

@@ -28,8 +28,8 @@ import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.HypixelPetInfo import moe.nea.firmament.util.HypixelPetInfo
import moe.nea.firmament.util.LegacyFormattingCode import moe.nea.firmament.util.LegacyFormattingCode
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.appendLore import moe.nea.firmament.util.mc.appendLore
import moe.nea.firmament.util.item.displayNameAccordingToNbt import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.petData import moe.nea.firmament.util.petData
import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.withColor import moe.nea.firmament.util.withColor

View File

@@ -33,10 +33,10 @@ import moe.nea.firmament.gui.hud.MoulConfigHud
import moe.nea.firmament.util.LegacyTagParser 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.appendLore import moe.nea.firmament.util.mc.appendLore
import moe.nea.firmament.util.item.setCustomName import moe.nea.firmament.util.mc.setCustomName
import moe.nea.firmament.util.item.setSkullOwner import moe.nea.firmament.util.mc.setSkullOwner
import moe.nea.firmament.util.modifyLore import moe.nea.firmament.util.mc.modifyLore
import moe.nea.firmament.util.skyblockId import moe.nea.firmament.util.skyblockId
object ItemCache : IReloadable { object ItemCache : IReloadable {

View File

@@ -0,0 +1,8 @@
package moe.nea.firmament.util.accessors
import net.minecraft.client.gui.hud.ChatHud
import net.minecraft.client.gui.hud.ChatHudLine
import moe.nea.firmament.mixins.accessor.AccessorChatHud
val ChatHud.messages: MutableList<ChatHudLine>
get() = (this as AccessorChatHud).messages_firmament

View File

@@ -0,0 +1,28 @@
package moe.nea.firmament.util.mc
import java.util.Spliterator
import java.util.Spliterators
import net.minecraft.inventory.Inventory
import net.minecraft.item.ItemStack
val Inventory.indices get() = 0 until size()
val Inventory.iterableView
get() = object : Iterable<ItemStack> {
override fun spliterator(): Spliterator<ItemStack> {
return Spliterators.spliterator(iterator(), size().toLong(), 0)
}
override fun iterator(): Iterator<ItemStack> {
return object : Iterator<ItemStack> {
var i = 0
override fun hasNext(): Boolean {
return i < size()
}
override fun next(): ItemStack {
if (!hasNext()) throw NoSuchElementException()
return getStack(i++)
}
}
}
}

View File

@@ -1,13 +1,7 @@
package moe.nea.firmament.util.mc
package moe.nea.firmament.util
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtList
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.util.item.loreAccordingToNbt
fun ItemStack.appendLore(args: List<Text>) { fun ItemStack.appendLore(args: List<Text>) {
if (args.isEmpty()) return if (args.isEmpty()) return

View File

@@ -1,6 +1,4 @@
package moe.nea.firmament.util.mc
package moe.nea.firmament.util.item
import net.minecraft.component.DataComponentTypes import net.minecraft.component.DataComponentTypes
import net.minecraft.component.type.LoreComponent import net.minecraft.component.type.LoreComponent

View File

@@ -1,8 +1,6 @@
@file:UseSerializers(DashlessUUIDSerializer::class, InstantAsLongSerializer::class) @file:UseSerializers(DashlessUUIDSerializer::class, InstantAsLongSerializer::class)
package moe.nea.firmament.util.item package moe.nea.firmament.util.mc
import com.mojang.authlib.GameProfile import com.mojang.authlib.GameProfile
import com.mojang.authlib.minecraft.MinecraftProfileTexture import com.mojang.authlib.minecraft.MinecraftProfileTexture

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import java.util.regex.Matcher import java.util.regex.Matcher
@@ -10,12 +8,12 @@ import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? = inline fun <T> String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? =
regex.matchEntire(this)?.let(block) regex.matchEntire(this)?.let(block)
inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? = inline fun <T> Pattern.useMatch(string: String, block: Matcher.() -> T): T? =
matcher(string) matcher(string)
.takeIf(Matcher::matches) .takeIf(Matcher::matches)
?.let(block) ?.let(block)
@Language("RegExp") @Language("RegExp")
val TIME_PATTERN = "[0-9]+[ms]" val TIME_PATTERN = "[0-9]+[ms]"
@@ -25,31 +23,33 @@ val SHORT_NUMBER_FORMAT = "[0-9]+(?:,[0-9]+)*(?:\\.[0-9]+)?[kKmMbB]?"
val siScalars = mapOf( val siScalars = mapOf(
'k' to 1_000.0, 'k' to 1_000.0,
'K' to 1_000.0, 'K' to 1_000.0,
'm' to 1_000_000.0, 'm' to 1_000_000.0,
'M' to 1_000_000.0, 'M' to 1_000_000.0,
'b' to 1_000_000_000.0, 'b' to 1_000_000_000.0,
'B' to 1_000_000_000.0, 'B' to 1_000_000_000.0,
) )
fun parseTimePattern(text: String): Duration { fun parseTimePattern(text: String): Duration {
val length = text.dropLast(1).toInt() val length = text.dropLast(1).toInt()
return when (text.last()) { return when (text.last()) {
'm' -> length.minutes 'm' -> length.minutes
's' -> length.seconds 's' -> length.seconds
else -> error("Invalid pattern for time $text") else -> error("Invalid pattern for time $text")
} }
} }
fun parseShortNumber(string: String): Double { fun parseShortNumber(string: String): Double {
var k = string.replace(",", "") if (string.startsWith("-")) return -parseShortNumber(string.substring(1))
val scalar = k.last() if (string.startsWith("+")) return parseShortNumber(string.substring(1))
var scalarMultiplier = siScalars[scalar] var k = string.replace(",", "")
if (scalarMultiplier == null) { val scalar = k.last()
scalarMultiplier = 1.0 var scalarMultiplier = siScalars[scalar]
} else { if (scalarMultiplier == null) {
k = k.dropLast(1) scalarMultiplier = 1.0
} } else {
return k.toDouble() * scalarMultiplier k = k.dropLast(1)
}
return k.toDouble() * scalarMultiplier
} }

View File

@@ -0,0 +1,110 @@
package moe.nea.firmament.util.skyblock
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import net.minecraft.client.gui.screen.ingame.GenericContainerScreen
import net.minecraft.text.HoverEvent
import net.minecraft.text.Text
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.ChestInventoryUpdateEvent
import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.repo.ItemNameLookup
import moe.nea.firmament.util.SHORT_NUMBER_FORMAT
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.data.ProfileSpecificDataHolder
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.iterableView
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.parseShortNumber
import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.unformattedString
import moe.nea.firmament.util.useMatch
object SackUtil {
@Serializable
data class SackContents(
// TODO: store the certainty of knowledge for each item.
val contents: MutableMap<SkyblockId, Long> = mutableMapOf(),
// val sackTypes:
)
object Store : ProfileSpecificDataHolder<SackContents>(serializer(), "Sacks", ::SackContents)
val items get() = Store.data?.contents ?: mutableMapOf()
val storedRegex = "^Stored: (?<stored>$SHORT_NUMBER_FORMAT)/(?<max>$SHORT_NUMBER_FORMAT)$".toPattern()
@Subscribe
fun storeDataFromInventory(event: ChestInventoryUpdateEvent) {
val screen = event.inventory as? GenericContainerScreen ?: return
if (!screen.title.unformattedString.endsWith(" Sack")) return
val inv = screen.screenHandler?.inventory ?: return
if (inv.size() < 18) return
val backSlot = inv.getStack(inv.size() - 5)
if (backSlot.displayNameAccordingToNbt.unformattedString != "Go Back") return
if (backSlot.loreAccordingToNbt.map { it.unformattedString } != listOf("To Sack of Sacks")) return
for (itemStack in inv.iterableView) {
// TODO: handle runes and gemstones
val stored = itemStack.loreAccordingToNbt.firstNotNullOfOrNull {
storedRegex.useMatch(it.unformattedString) {
val stored = parseShortNumber(group("stored")).toLong()
val max = parseShortNumber(group("max")).toLong()
stored
}
} ?: continue
val itemId = itemStack.skyBlockId ?: continue
items[itemId] = stored
}
Store.markDirty()
}
@Subscribe
fun updateFromChat(event: ProcessChatEvent) {
if (!event.unformattedString.startsWith("[Sacks]")) return
val update = ChatUpdate()
event.text.siblings.forEach(update::updateFromHoverText)
}
data class SackUpdate(
val itemId: SkyblockId?,
val itemName: String,
val changeAmount: Long,
)
private class ChatUpdate {
val updates = mutableListOf<SackUpdate>()
var foundAdded = false
var foundRemoved = false
fun updateFromCleanText(cleanedText: String) {
cleanedText.split("\n").forEach { line ->
changePattern.useMatch(line) {
val amount = parseShortNumber(group("amount")).toLong()
val itemName = group("itemName")
val itemId = ItemNameLookup.guessItemByName(itemName, false)
updates.add(SackUpdate(itemId, itemName, amount))
}
}
}
fun updateFromHoverText(text: Text) {
text.siblings.forEach(::updateFromHoverText)
val hoverText = text.style.hoverEvent?.getValue(HoverEvent.Action.SHOW_TEXT) ?: return
val cleanedText = hoverText.unformattedString
if (cleanedText.startsWith("Added items:\n")) {
if (!foundAdded) {
updateFromCleanText(cleanedText)
foundAdded = true
}
}
if (cleanedText.startsWith("Removed items:\n")) {
if (!foundRemoved) {
updateFromCleanText(cleanedText)
foundRemoved = true
}
}
}
}
val changePattern = " (?<amount>[+\\-]$SHORT_NUMBER_FORMAT) (?<itemName>[^(]+) \\(.*\\)".toPattern()
}

View File

@@ -1,10 +1,7 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import net.minecraft.text.MutableText import net.minecraft.text.MutableText
import net.minecraft.text.PlainTextContent import net.minecraft.text.PlainTextContent
import net.minecraft.text.Style
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.text.TranslatableTextContent import net.minecraft.text.TranslatableTextContent
import net.minecraft.util.Formatting import net.minecraft.util.Formatting
@@ -12,106 +9,107 @@ import moe.nea.firmament.Firmament
class TextMatcher(text: Text) { class TextMatcher(text: Text) {
data class State( data class State(
var iterator: MutableList<Text>, var iterator: MutableList<Text>,
var currentText: Text?, var currentText: Text?,
var offset: Int, var offset: Int,
var textContent: String, var textContent: String,
) )
var state = State( var state = State(
mutableListOf(text), mutableListOf(text),
null, null,
0, 0,
"" ""
) )
fun pollChunk(): Boolean { fun pollChunk(): Boolean {
val firstOrNull = state.iterator.removeFirstOrNull() ?: return false val firstOrNull = state.iterator.removeFirstOrNull() ?: return false
state.offset = 0 state.offset = 0
state.currentText = firstOrNull state.currentText = firstOrNull
state.textContent = when (val content = firstOrNull.content) { state.textContent = when (val content = firstOrNull.content) {
is PlainTextContent.Literal -> content.string is PlainTextContent.Literal -> content.string
else -> { else -> {
Firmament.logger.warn("TextContent of type ${content.javaClass} not understood.") Firmament.logger.warn("TextContent of type ${content.javaClass} not understood.")
return false return false
} }
} }
state.iterator.addAll(0, firstOrNull.siblings) state.iterator.addAll(0, firstOrNull.siblings)
return true return true
} }
fun pollChunks(): Boolean { fun pollChunks(): Boolean {
while (state.offset !in state.textContent.indices) { while (state.offset !in state.textContent.indices) {
if (!pollChunk()) { if (!pollChunk()) {
return false return false
} }
} }
return true return true
} }
fun pollChar(): Char? { fun pollChar(): Char? {
if (!pollChunks()) return null if (!pollChunks()) return null
return state.textContent[state.offset++] return state.textContent[state.offset++]
} }
fun expectString(string: String): Boolean { fun expectString(string: String): Boolean {
var found = "" var found = ""
while (found.length < string.length) { while (found.length < string.length) {
if (!pollChunks()) return false if (!pollChunks()) return false
val takeable = state.textContent.drop(state.offset).take(string.length - found.length) val takeable = state.textContent.drop(state.offset).take(string.length - found.length)
state.offset += takeable.length state.offset += takeable.length
found += takeable found += takeable
} }
return found == string return found == string
} }
} }
val formattingChars = "kmolnrKMOLNR".toSet() val formattingChars = "kmolnrKMOLNR".toSet()
fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String { fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String {
var nextParagraph = indexOf('§') var nextParagraph = indexOf('§')
if (nextParagraph < 0) return this.toString() if (nextParagraph < 0) return this.toString()
val stringBuffer = StringBuilder(this.length) val stringBuffer = StringBuilder(this.length)
var readIndex = 0 var readIndex = 0
while (nextParagraph >= 0) { while (nextParagraph >= 0) {
stringBuffer.append(this, readIndex, nextParagraph) stringBuffer.append(this, readIndex, nextParagraph)
if (keepNonColorCodes && nextParagraph + 1 < length && this[nextParagraph + 1] in formattingChars) { if (keepNonColorCodes && nextParagraph + 1 < length && this[nextParagraph + 1] in formattingChars) {
readIndex = nextParagraph readIndex = nextParagraph
nextParagraph = indexOf('§', startIndex = readIndex + 1) nextParagraph = indexOf('§', startIndex = readIndex + 1)
} else { } else {
readIndex = nextParagraph + 2 readIndex = nextParagraph + 2
nextParagraph = indexOf('§', startIndex = readIndex) nextParagraph = indexOf('§', startIndex = readIndex)
} }
if (readIndex > this.length) if (readIndex > this.length)
readIndex = this.length readIndex = this.length
} }
stringBuffer.append(this, readIndex, this.length) stringBuffer.append(this, readIndex, this.length)
return stringBuffer.toString() return stringBuffer.toString()
} }
val Text.unformattedString: String val Text.unformattedString: String
get() = string.removeColorCodes() get() = string.removeColorCodes()
fun Text.allSiblings(): List<Text> = listOf(this) + siblings.flatMap { it.allSiblings() }
fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) } fun MutableText.withColor(formatting: Formatting) = this.styled { it.withColor(formatting).withItalic(false) }
fun Text.transformEachRecursively(function: (Text) -> Text): Text { fun Text.transformEachRecursively(function: (Text) -> Text): Text {
val c = this.content val c = this.content
if (c is TranslatableTextContent) { if (c is TranslatableTextContent) {
return Text.translatableWithFallback(c.key, c.fallback, *c.args.map { return Text.translatableWithFallback(c.key, c.fallback, *c.args.map {
(if (it is Text) it else Text.literal(it.toString())).transformEachRecursively(function) (if (it is Text) it else Text.literal(it.toString())).transformEachRecursively(function)
}.toTypedArray()).also { new -> }.toTypedArray()).also { new ->
new.style = this.style new.style = this.style
new.siblings.clear() new.siblings.clear()
this.siblings.forEach { child -> this.siblings.forEach { child ->
new.siblings.add(child.transformEachRecursively(function)) new.siblings.add(child.transformEachRecursively(function))
} }
} }
} }
return function(this.copy().also { it.siblings.clear() }).also { tt -> return function(this.copy().also { it.siblings.clear() }).also { tt ->
this.siblings.forEach { this.siblings.forEach {
tt.siblings.add(it.transformEachRecursively(function)) tt.siblings.add(it.transformEachRecursively(function))
} }
} }
} }