diff options
22 files changed, 356 insertions, 183 deletions
diff --git a/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java b/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java index f1d4a80..6c854d4 100644 --- a/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java +++ b/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java @@ -1,13 +1,12 @@ package moe.nea.firmament.mixins; -import com.llamalad7.mixinextras.sugar.Local; +import moe.nea.firmament.events.ChestInventoryUpdateEvent; import moe.nea.firmament.events.PlayerInventoryUpdate; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientCommonNetworkHandler; import net.minecraft.client.network.ClientConnectionState; import net.minecraft.client.network.ClientPlayNetworkHandler; -import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.network.ClientConnection; import net.minecraft.network.packet.s2c.play.InventoryS2CPacket; import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; @@ -18,36 +17,40 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ClientPlayNetworkHandler.class) public abstract class SlotUpdateListener extends ClientCommonNetworkHandler { - protected SlotUpdateListener(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) { - super(client, connection, connectionState); - } + protected SlotUpdateListener(MinecraftClient client, ClientConnection connection, ClientConnectionState connectionState) { + super(client, connection, connectionState); + } - @Inject( - method = "onScreenHandlerSlotUpdate", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/tutorial/TutorialManager;onSlotUpdate(Lnet/minecraft/item/ItemStack;)V")) - private void onSingleSlotUpdate( - ScreenHandlerSlotUpdateS2CPacket packet, - CallbackInfo ci) { - var player = this.client.player; - assert player != null; - if (packet.getSyncId() == ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID - || packet.getSyncId() == 0) { - PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Single(packet.getSlot(), packet.getStack())); - } else if (packet.getSyncId() == player.currentScreenHandler.syncId) { - // TODO: dispatch single chest slot - } - } + @Inject( + method = "onScreenHandlerSlotUpdate", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/tutorial/TutorialManager;onSlotUpdate(Lnet/minecraft/item/ItemStack;)V")) + private void onSingleSlotUpdate( + ScreenHandlerSlotUpdateS2CPacket packet, + CallbackInfo ci) { + var player = this.client.player; + assert player != null; + if (packet.getSyncId() == ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID + || packet.getSyncId() == 0) { + PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Single(packet.getSlot(), packet.getStack())); + } else if (packet.getSyncId() == player.currentScreenHandler.syncId) { + ChestInventoryUpdateEvent.Companion.publish( + new ChestInventoryUpdateEvent.Single(packet.getSlot(), packet.getStack()) + ); + } + } - @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", - shift = At.Shift.AFTER)) - private void onMultiSlotUpdate(InventoryS2CPacket packet, CallbackInfo ci) { - var player = this.client.player; - assert player != null; - if (packet.getSyncId() == 0) { - PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Multi(packet.getContents())); - } else if (packet.getSyncId() == player.currentScreenHandler.syncId) { - // TODO: dispatch multi chest - } - } + @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", + shift = At.Shift.AFTER)) + private void onMultiSlotUpdate(InventoryS2CPacket packet, CallbackInfo ci) { + var player = this.client.player; + assert player != null; + if (packet.getSyncId() == 0) { + PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Multi(packet.getContents())); + } else if (packet.getSyncId() == player.currentScreenHandler.syncId) { + ChestInventoryUpdateEvent.Companion.publish( + new ChestInventoryUpdateEvent.Multi(packet.getContents()) + ); + } + } } diff --git a/src/main/java/moe/nea/firmament/mixins/accessor/AccessorChatHud.java b/src/main/java/moe/nea/firmament/mixins/accessor/AccessorChatHud.java new file mode 100644 index 0000000..72a72f0 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/accessor/AccessorChatHud.java @@ -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(); +} diff --git a/src/main/kotlin/commands/rome.kt b/src/main/kotlin/commands/rome.kt index afb3cae..cc4f4ba 100644 --- a/src/main/kotlin/commands/rome.kt +++ b/src/main/kotlin/commands/rome.kt @@ -4,7 +4,9 @@ import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.arguments.StringArgumentType.string import io.ktor.client.statement.bodyAsText import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import net.minecraft.nbt.NbtOps import net.minecraft.text.Text +import net.minecraft.text.TextCodecs import moe.nea.firmament.apis.UrsaManager import moe.nea.firmament.events.CommandEvent 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.ScreenUtil 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.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") { thenExecute { source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.profile", SBData.profileId)) diff --git a/src/main/kotlin/events/ChestInventoryUpdateEvent.kt b/src/main/kotlin/events/ChestInventoryUpdateEvent.kt new file mode 100644 index 0000000..ddf54fc --- /dev/null +++ b/src/main/kotlin/events/ChestInventoryUpdateEvent.kt @@ -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 +} diff --git a/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt b/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt index 06caf86..1824225 100644 --- a/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt +++ b/src/main/kotlin/features/events/carnival/MinesweeperHelper.kt @@ -27,7 +27,7 @@ import moe.nea.firmament.util.MC import moe.nea.firmament.util.MoulConfigUtils import moe.nea.firmament.util.ScreenUtil 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.setSkyBlockFirmamentUiId import moe.nea.firmament.util.skyBlockId diff --git a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt index 55509f5..424f13b 100644 --- a/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt +++ b/src/main/kotlin/features/inventory/ItemRarityCosmetics.kt @@ -13,7 +13,7 @@ import moe.nea.firmament.events.SlotRenderEvents import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig 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.memoizeIdentity import moe.nea.firmament.util.unformattedString diff --git a/src/main/kotlin/features/inventory/SlotLocking.kt b/src/main/kotlin/features/inventory/SlotLocking.kt index a50d8fb..9f13af8 100644 --- a/src/main/kotlin/features/inventory/SlotLocking.kt +++ b/src/main/kotlin/features/inventory/SlotLocking.kt @@ -28,8 +28,8 @@ import moe.nea.firmament.util.MC import moe.nea.firmament.util.SBData import moe.nea.firmament.util.SkyBlockIsland import moe.nea.firmament.util.data.ProfileSpecificDataHolder -import moe.nea.firmament.util.item.displayNameAccordingToNbt -import moe.nea.firmament.util.item.loreAccordingToNbt +import moe.nea.firmament.util.mc.displayNameAccordingToNbt +import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.json.DashlessUUIDSerializer import moe.nea.firmament.util.skyblockUUID import moe.nea.firmament.util.unformattedString diff --git a/src/main/kotlin/features/mining/PickaxeAbility.kt b/src/main/kotlin/features/mining/PickaxeAbility.kt index 7879f2d..ca8ae6b 100644 --- a/src/main/kotlin/features/mining/PickaxeAbility.kt +++ b/src/main/kotlin/features/mining/PickaxeAbility.kt @@ -21,8 +21,8 @@ import moe.nea.firmament.util.SHORT_NUMBER_FORMAT import moe.nea.firmament.util.TIME_PATTERN import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.extraAttributes -import moe.nea.firmament.util.item.displayNameAccordingToNbt -import moe.nea.firmament.util.item.loreAccordingToNbt +import moe.nea.firmament.util.mc.displayNameAccordingToNbt +import moe.nea.firmament.util.mc.loreAccordingToNbt import moe.nea.firmament.util.parseShortNumber import moe.nea.firmament.util.parseTimePattern import moe.nea.firmament.util.render.RenderCircleProgress diff --git a/src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt b/src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt index 3b7cb96..2aca8e8 100644 --- a/src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt +++ b/src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt @@ -19,7 +19,7 @@ import moe.nea.firmament.events.TickEvent import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig 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 object CustomSkyBlockTextures : FirmamentFeature { diff --git a/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt b/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt index c89931e..100aaf4 100644 --- a/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt +++ b/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt @@ -5,8 +5,8 @@ import com.google.gson.JsonElement import net.minecraft.item.ItemStack import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtString -import moe.nea.firmament.util.item.displayNameAccordingToNbt -import moe.nea.firmament.util.item.loreAccordingToNbt +import moe.nea.firmament.util.mc.displayNameAccordingToNbt +import moe.nea.firmament.util.mc.loreAccordingToNbt data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate { override fun test(stack: ItemStack): Boolean { diff --git a/src/main/kotlin/features/texturepack/LorePredicate.kt b/src/main/kotlin/features/texturepack/LorePredicate.kt index 13e3974..f2b7e76 100644 --- a/src/main/kotlin/features/texturepack/LorePredicate.kt +++ b/src/main/kotlin/features/texturepack/LorePredicate.kt @@ -3,7 +3,7 @@ package moe.nea.firmament.features.texturepack import com.google.gson.JsonElement 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 { object Parser : FirmamentModelPredicateParser { diff --git a/src/main/kotlin/gui/entity/ModifyEquipment.kt b/src/main/kotlin/gui/entity/ModifyEquipment.kt index 73e450e..11dfb52 100644 --- a/src/main/kotlin/gui/entity/ModifyEquipment.kt +++ b/src/main/kotlin/gui/entity/ModifyEquipment.kt @@ -12,8 +12,8 @@ import net.minecraft.item.ItemStack import net.minecraft.item.Items import moe.nea.firmament.rei.SBItemStack import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.item.setEncodedSkullOwner -import moe.nea.firmament.util.item.zeroUUID +import moe.nea.firmament.util.mc.setEncodedSkullOwner +import moe.nea.firmament.util.mc.zeroUUID object ModifyEquipment : EntityModifier { val names = mapOf( diff --git a/src/main/kotlin/rei/SBItemEntryDefinition.kt b/src/main/kotlin/rei/SBItemEntryDefinition.kt index 077eeb1..3df8fa3 100644 --- a/src/main/kotlin/rei/SBItemEntryDefinition.kt +++ b/src/main/kotlin/rei/SBItemEntryDefinition.kt @@ -28,8 +28,8 @@ import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.HypixelPetInfo import moe.nea.firmament.util.LegacyFormattingCode import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.appendLore -import moe.nea.firmament.util.item.displayNameAccordingToNbt +import moe.nea.firmament.util.mc.appendLore +import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.petData import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.withColor diff --git a/src/main/kotlin/repo/ItemCache.kt b/src/main/kotlin/repo/ItemCache.kt index 08143be..c86934c 100644 --- a/src/main/kotlin/repo/ItemCache.kt +++ b/src/main/kotlin/repo/ItemCache.kt @@ -33,10 +33,10 @@ import moe.nea.firmament.gui.hud.MoulConfigHud import moe.nea.firmament.util.LegacyTagParser import moe.nea.firmament.util.MC import moe.nea.firmament.util.SkyblockId -import moe.nea.firmament.util.appendLore -import moe.nea.firmament.util.item.setCustomName -import moe.nea.firmament.util.item.setSkullOwner -import moe.nea.firmament.util.modifyLore +import moe.nea.firmament.util.mc.appendLore +import moe.nea.firmament.util.mc.setCustomName +import moe.nea.firmament.util.mc.setSkullOwner +import moe.nea.firmament.util.mc.modifyLore import moe.nea.firmament.util.skyblockId object ItemCache : IReloadable { diff --git a/src/main/kotlin/util/accessors/chathud.kt b/src/main/kotlin/util/accessors/chathud.kt new file mode 100644 index 0000000..effac7d --- /dev/null +++ b/src/main/kotlin/util/accessors/chathud.kt @@ -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 diff --git a/src/main/kotlin/util/mc/InventoryUtil.kt b/src/main/kotlin/util/mc/InventoryUtil.kt new file mode 100644 index 0000000..74f7b9f --- /dev/null +++ b/src/main/kotlin/util/mc/InventoryUtil.kt @@ -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++) + } + } + } + } diff --git a/src/main/kotlin/util/ItemUtil.kt b/src/main/kotlin/util/mc/ItemUtil.kt index 40d6198..13519cf 100644 --- a/src/main/kotlin/util/ItemUtil.kt +++ b/src/main/kotlin/util/mc/ItemUtil.kt @@ -1,13 +1,7 @@ - - -package moe.nea.firmament.util +package moe.nea.firmament.util.mc import net.minecraft.item.ItemStack -import net.minecraft.nbt.NbtCompound -import net.minecraft.nbt.NbtList import net.minecraft.text.Text -import moe.nea.firmament.util.item.loreAccordingToNbt - fun ItemStack.appendLore(args: List<Text>) { if (args.isEmpty()) return diff --git a/src/main/kotlin/util/item/NbtItemData.kt b/src/main/kotlin/util/mc/NbtItemData.kt index f7f259d..e8a908f 100644 --- a/src/main/kotlin/util/item/NbtItemData.kt +++ b/src/main/kotlin/util/mc/NbtItemData.kt @@ -1,6 +1,4 @@ - - -package moe.nea.firmament.util.item +package moe.nea.firmament.util.mc import net.minecraft.component.DataComponentTypes import net.minecraft.component.type.LoreComponent diff --git a/src/main/kotlin/util/item/SkullItemData.kt b/src/main/kotlin/util/mc/SkullItemData.kt index ddab88e..0405b65 100644 --- a/src/main/kotlin/util/item/SkullItemData.kt +++ b/src/main/kotlin/util/mc/SkullItemData.kt @@ -1,8 +1,6 @@ - - @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.minecraft.MinecraftProfileTexture diff --git a/src/main/kotlin/util/regex.kt b/src/main/kotlin/util/regex.kt index 3ce5bd8..78c90e8 100644 --- a/src/main/kotlin/util/regex.kt +++ b/src/main/kotlin/util/regex.kt @@ -1,5 +1,3 @@ - - package moe.nea.firmament.util import java.util.regex.Matcher @@ -10,12 +8,12 @@ import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds 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? = - matcher(string) - .takeIf(Matcher::matches) - ?.let(block) + matcher(string) + .takeIf(Matcher::matches) + ?.let(block) @Language("RegExp") val TIME_PATTERN = "[0-9]+[ms]" @@ -25,31 +23,33 @@ val SHORT_NUMBER_FORMAT = "[0-9]+(?:,[0-9]+)*(?:\\.[0-9]+)?[kKmMbB]?" val siScalars = mapOf( - 'k' to 1_000.0, - 'K' to 1_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, + 'k' to 1_000.0, + 'K' to 1_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, ) fun parseTimePattern(text: String): Duration { - val length = text.dropLast(1).toInt() - return when (text.last()) { - 'm' -> length.minutes - 's' -> length.seconds - else -> error("Invalid pattern for time $text") - } + val length = text.dropLast(1).toInt() + return when (text.last()) { + 'm' -> length.minutes + 's' -> length.seconds + else -> error("Invalid pattern for time $text") + } } fun parseShortNumber(string: String): Double { - var k = string.replace(",", "") - val scalar = k.last() - var scalarMultiplier = siScalars[scalar] - if (scalarMultiplier == null) { - scalarMultiplier = 1.0 - } else { - k = k.dropLast(1) - } - return k.toDouble() * scalarMultiplier + if (string.startsWith("-")) return -parseShortNumber(string.substring(1)) + if (string.startsWith("+")) return parseShortNumber(string.substring(1)) + var k = string.replace(",", "") + val scalar = k.last() + var scalarMultiplier = siScalars[scalar] + if (scalarMultiplier == null) { + scalarMultiplier = 1.0 + } else { + k = k.dropLast(1) + } + return k.toDouble() * scalarMultiplier } diff --git a/src/main/kotlin/util/skyblock/SackUtil.kt b/src/main/kotlin/util/skyblock/SackUtil.kt new file mode 100644 index 0000000..2679949 --- /dev/null +++ b/src/main/kotlin/util/skyblock/SackUtil.kt @@ -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() +} diff --git a/src/main/kotlin/util/textutil.kt b/src/main/kotlin/util/textutil.kt index a05733c..36924a6 100644 --- a/src/main/kotlin/util/textutil.kt +++ b/src/main/kotlin/util/textutil.kt @@ -1,10 +1,7 @@ - - package moe.nea.firmament.util import net.minecraft.text.MutableText import net.minecraft.text.PlainTextContent -import net.minecraft.text.Style import net.minecraft.text.Text import net.minecraft.text.TranslatableTextContent import net.minecraft.util.Formatting @@ -12,106 +9,107 @@ import moe.nea.firmament.Firmament class TextMatcher(text: Text) { - data class State( - var iterator: MutableList<Text>, - var currentText: Text?, - var offset: Int, - var textContent: String, - ) - - var state = State( - mutableListOf(text), - null, - 0, - "" - ) - - fun pollChunk(): Boolean { - val firstOrNull = state.iterator.removeFirstOrNull() ?: return false - state.offset = 0 - state.currentText = firstOrNull - state.textContent = when (val content = firstOrNull.content) { - is PlainTextContent.Literal -> content.string - else -> { - Firmament.logger.warn("TextContent of type ${content.javaClass} not understood.") - return false - } - } - state.iterator.addAll(0, firstOrNull.siblings) - return true - } - - fun pollChunks(): Boolean { - while (state.offset !in state.textContent.indices) { - if (!pollChunk()) { - return false - } - } - return true - } - - fun pollChar(): Char? { - if (!pollChunks()) return null - return state.textContent[state.offset++] - } - - - fun expectString(string: String): Boolean { - var found = "" - while (found.length < string.length) { - if (!pollChunks()) return false - val takeable = state.textContent.drop(state.offset).take(string.length - found.length) - state.offset += takeable.length - found += takeable - } - return found == string - } + data class State( + var iterator: MutableList<Text>, + var currentText: Text?, + var offset: Int, + var textContent: String, + ) + + var state = State( + mutableListOf(text), + null, + 0, + "" + ) + + fun pollChunk(): Boolean { + val firstOrNull = state.iterator.removeFirstOrNull() ?: return false + state.offset = 0 + state.currentText = firstOrNull + state.textContent = when (val content = firstOrNull.content) { + is PlainTextContent.Literal -> content.string + else -> { + Firmament.logger.warn("TextContent of type ${content.javaClass} not understood.") + return false + } + } + state.iterator.addAll(0, firstOrNull.siblings) + return true + } + + fun pollChunks(): Boolean { + while (state.offset !in state.textContent.indices) { + if (!pollChunk()) { + return false + } + } + return true + } + + fun pollChar(): Char? { + if (!pollChunks()) return null + return state.textContent[state.offset++] + } + + + fun expectString(string: String): Boolean { + var found = "" + while (found.length < string.length) { + if (!pollChunks()) return false + val takeable = state.textContent.drop(state.offset).take(string.length - found.length) + state.offset += takeable.length + found += takeable + } + return found == string + } } val formattingChars = "kmolnrKMOLNR".toSet() fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String { - var nextParagraph = indexOf('§') - if (nextParagraph < 0) return this.toString() - val stringBuffer = StringBuilder(this.length) - var readIndex = 0 - while (nextParagraph >= 0) { - stringBuffer.append(this, readIndex, nextParagraph) - if (keepNonColorCodes && nextParagraph + 1 < length && this[nextParagraph + 1] in formattingChars) { - readIndex = nextParagraph - nextParagraph = indexOf('§', startIndex = readIndex + 1) - } else { - readIndex = nextParagraph + 2 - nextParagraph = indexOf('§', startIndex = readIndex) - } - if (readIndex > this.length) - readIndex = this.length - } - stringBuffer.append(this, readIndex, this.length) - return stringBuffer.toString() + var nextParagraph = indexOf('§') + if (nextParagraph < 0) return this.toString() + val stringBuffer = StringBuilder(this.length) + var readIndex = 0 + while (nextParagraph >= 0) { + stringBuffer.append(this, readIndex, nextParagraph) + if (keepNonColorCodes && nextParagraph + 1 < length && this[nextParagraph + 1] in formattingChars) { + readIndex = nextParagraph + nextParagraph = indexOf('§', startIndex = readIndex + 1) + } else { + readIndex = nextParagraph + 2 + nextParagraph = indexOf('§', startIndex = readIndex) + } + if (readIndex > this.length) + readIndex = this.length + } + stringBuffer.append(this, readIndex, this.length) + return stringBuffer.toString() } 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 Text.transformEachRecursively(function: (Text) -> Text): Text { - val c = this.content - if (c is TranslatableTextContent) { - return Text.translatableWithFallback(c.key, c.fallback, *c.args.map { - (if (it is Text) it else Text.literal(it.toString())).transformEachRecursively(function) - }.toTypedArray()).also { new -> - new.style = this.style - new.siblings.clear() - this.siblings.forEach { child -> - new.siblings.add(child.transformEachRecursively(function)) - } - } - } - return function(this.copy().also { it.siblings.clear() }).also { tt -> - this.siblings.forEach { - tt.siblings.add(it.transformEachRecursively(function)) - } - } + val c = this.content + if (c is TranslatableTextContent) { + return Text.translatableWithFallback(c.key, c.fallback, *c.args.map { + (if (it is Text) it else Text.literal(it.toString())).transformEachRecursively(function) + }.toTypedArray()).also { new -> + new.style = this.style + new.siblings.clear() + this.siblings.forEach { child -> + new.siblings.add(child.transformEachRecursively(function)) + } + } + } + return function(this.copy().also { it.siblings.clear() }).also { tt -> + this.siblings.forEach { + tt.siblings.add(it.transformEachRecursively(function)) + } + } } |