From a4eac70118fc25334c9352712fe3c7944b8bed1d Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Tue, 1 Oct 2024 18:00:43 +0200 Subject: Add basic sack util [no changelog] --- .../nea/firmament/mixins/SlotUpdateListener.java | 67 ++++---- .../firmament/mixins/accessor/AccessorChatHud.java | 14 ++ src/main/kotlin/commands/rome.kt | 11 ++ .../kotlin/events/ChestInventoryUpdateEvent.kt | 11 ++ .../features/events/carnival/MinesweeperHelper.kt | 2 +- .../features/inventory/ItemRarityCosmetics.kt | 2 +- src/main/kotlin/features/inventory/SlotLocking.kt | 4 +- src/main/kotlin/features/mining/PickaxeAbility.kt | 4 +- .../features/texturepack/CustomSkyBlockTextures.kt | 2 +- .../features/texturepack/DisplayNamePredicate.kt | 4 +- .../kotlin/features/texturepack/LorePredicate.kt | 2 +- src/main/kotlin/gui/entity/ModifyEquipment.kt | 4 +- src/main/kotlin/rei/SBItemEntryDefinition.kt | 4 +- src/main/kotlin/repo/ItemCache.kt | 8 +- src/main/kotlin/util/ItemUtil.kt | 26 --- src/main/kotlin/util/accessors/chathud.kt | 8 + src/main/kotlin/util/item/NbtItemData.kt | 24 --- src/main/kotlin/util/item/SkullItemData.kt | 90 ---------- src/main/kotlin/util/mc/InventoryUtil.kt | 28 ++++ src/main/kotlin/util/mc/ItemUtil.kt | 20 +++ src/main/kotlin/util/mc/NbtItemData.kt | 22 +++ src/main/kotlin/util/mc/SkullItemData.kt | 88 ++++++++++ src/main/kotlin/util/regex.kt | 54 +++--- src/main/kotlin/util/skyblock/SackUtil.kt | 110 ++++++++++++ src/main/kotlin/util/textutil.kt | 184 ++++++++++----------- 25 files changed, 483 insertions(+), 310 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/mixins/accessor/AccessorChatHud.java create mode 100644 src/main/kotlin/events/ChestInventoryUpdateEvent.kt delete mode 100644 src/main/kotlin/util/ItemUtil.kt create mode 100644 src/main/kotlin/util/accessors/chathud.kt delete mode 100644 src/main/kotlin/util/item/NbtItemData.kt delete mode 100644 src/main/kotlin/util/item/SkullItemData.kt create mode 100644 src/main/kotlin/util/mc/InventoryUtil.kt create mode 100644 src/main/kotlin/util/mc/ItemUtil.kt create mode 100644 src/main/kotlin/util/mc/NbtItemData.kt create mode 100644 src/main/kotlin/util/mc/SkullItemData.kt create mode 100644 src/main/kotlin/util/skyblock/SackUtil.kt 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 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() + data class Single(val slot: Int, val stack: ItemStack) : ChestInventoryUpdateEvent() + data class Multi(val contents: List) : 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/ItemUtil.kt b/src/main/kotlin/util/ItemUtil.kt deleted file mode 100644 index 40d6198..0000000 --- a/src/main/kotlin/util/ItemUtil.kt +++ /dev/null @@ -1,26 +0,0 @@ - - -package moe.nea.firmament.util - -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) { - if (args.isEmpty()) return - modifyLore { - val loreList = loreAccordingToNbt.toMutableList() - for (arg in args) { - loreList.add(arg) - } - loreList - } -} - -fun ItemStack.modifyLore(update: (List) -> List) { - val loreList = loreAccordingToNbt - loreAccordingToNbt = update(loreList) -} 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 + get() = (this as AccessorChatHud).messages_firmament diff --git a/src/main/kotlin/util/item/NbtItemData.kt b/src/main/kotlin/util/item/NbtItemData.kt deleted file mode 100644 index f7f259d..0000000 --- a/src/main/kotlin/util/item/NbtItemData.kt +++ /dev/null @@ -1,24 +0,0 @@ - - -package moe.nea.firmament.util.item - -import net.minecraft.component.DataComponentTypes -import net.minecraft.component.type.LoreComponent -import net.minecraft.item.ItemStack -import net.minecraft.text.Text - -var ItemStack.loreAccordingToNbt - get() = get(DataComponentTypes.LORE)?.lines ?: listOf() - set(value) { - set(DataComponentTypes.LORE, LoreComponent(value)) - } - -var ItemStack.displayNameAccordingToNbt: Text - get() = get(DataComponentTypes.CUSTOM_NAME) ?: get(DataComponentTypes.ITEM_NAME) ?: item.name - set(value) { - set(DataComponentTypes.CUSTOM_NAME, value) - } - -fun ItemStack.setCustomName(text: Text) { - set(DataComponentTypes.CUSTOM_NAME, text) -} diff --git a/src/main/kotlin/util/item/SkullItemData.kt b/src/main/kotlin/util/item/SkullItemData.kt deleted file mode 100644 index ddab88e..0000000 --- a/src/main/kotlin/util/item/SkullItemData.kt +++ /dev/null @@ -1,90 +0,0 @@ - - -@file:UseSerializers(DashlessUUIDSerializer::class, InstantAsLongSerializer::class) - -package moe.nea.firmament.util.item - -import com.mojang.authlib.GameProfile -import com.mojang.authlib.minecraft.MinecraftProfileTexture -import com.mojang.authlib.properties.Property -import java.util.UUID -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import kotlinx.serialization.encodeToString -import net.minecraft.component.DataComponentTypes -import net.minecraft.component.type.ProfileComponent -import net.minecraft.item.ItemStack -import net.minecraft.item.Items -import moe.nea.firmament.Firmament -import moe.nea.firmament.util.Base64Util.padToValidBase64 -import moe.nea.firmament.util.assertTrueOr -import moe.nea.firmament.util.json.DashlessUUIDSerializer -import moe.nea.firmament.util.json.InstantAsLongSerializer - -@Serializable -data class MinecraftProfileTextureKt( - val url: String, - val metadata: Map = mapOf(), -) - -@Serializable -data class MinecraftTexturesPayloadKt( - val textures: Map = mapOf(), - val profileId: UUID? = null, - val profileName: String? = null, - val isPublic: Boolean = true, - val timestamp: Instant = Clock.System.now(), -) - -fun GameProfile.setTextures(textures: MinecraftTexturesPayloadKt) { - val json = Firmament.json.encodeToString(textures) - val encoded = java.util.Base64.getEncoder().encodeToString(json.encodeToByteArray()) - properties.put(propertyTextures, Property(propertyTextures, encoded)) -} - -private val propertyTextures = "textures" - -fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) { - assert(this.item == Items.PLAYER_HEAD) - val gameProfile = GameProfile(uuid, "LameGuy123") - gameProfile.properties.put(propertyTextures, Property(propertyTextures, encodedData.padToValidBase64())) - this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) -} - -val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1") -fun createSkullItem(uuid: UUID, url: String) = ItemStack(Items.PLAYER_HEAD) - .also { it.setSkullOwner(uuid, url) } - -fun ItemStack.setSkullOwner(uuid: UUID, url: String) { - assert(this.item == Items.PLAYER_HEAD) - val gameProfile = GameProfile(uuid, "nea89") - gameProfile.setTextures( - MinecraftTexturesPayloadKt( - textures = mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)), - profileId = uuid, - profileName = "nea89", - ) - ) - this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) -} - - -fun decodeProfileTextureProperty(property: Property): MinecraftTexturesPayloadKt? { - assertTrueOr(property.name == propertyTextures) { return null } - return try { - var encodedF: String = property.value - while (encodedF.length % 4 != 0 && encodedF.last() == '=') { - encodedF = encodedF.substring(0, encodedF.length - 1) - } - val json = java.util.Base64.getDecoder().decode(encodedF).decodeToString() - Firmament.json.decodeFromString(json) - } catch (e: Exception) { - // Malformed profile data - if (Firmament.DEBUG) - e.printStackTrace() - null - } -} - 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 { + override fun spliterator(): Spliterator { + return Spliterators.spliterator(iterator(), size().toLong(), 0) + } + + override fun iterator(): Iterator { + return object : Iterator { + 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/mc/ItemUtil.kt b/src/main/kotlin/util/mc/ItemUtil.kt new file mode 100644 index 0000000..13519cf --- /dev/null +++ b/src/main/kotlin/util/mc/ItemUtil.kt @@ -0,0 +1,20 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.item.ItemStack +import net.minecraft.text.Text + +fun ItemStack.appendLore(args: List) { + if (args.isEmpty()) return + modifyLore { + val loreList = loreAccordingToNbt.toMutableList() + for (arg in args) { + loreList.add(arg) + } + loreList + } +} + +fun ItemStack.modifyLore(update: (List) -> List) { + val loreList = loreAccordingToNbt + loreAccordingToNbt = update(loreList) +} diff --git a/src/main/kotlin/util/mc/NbtItemData.kt b/src/main/kotlin/util/mc/NbtItemData.kt new file mode 100644 index 0000000..e8a908f --- /dev/null +++ b/src/main/kotlin/util/mc/NbtItemData.kt @@ -0,0 +1,22 @@ +package moe.nea.firmament.util.mc + +import net.minecraft.component.DataComponentTypes +import net.minecraft.component.type.LoreComponent +import net.minecraft.item.ItemStack +import net.minecraft.text.Text + +var ItemStack.loreAccordingToNbt + get() = get(DataComponentTypes.LORE)?.lines ?: listOf() + set(value) { + set(DataComponentTypes.LORE, LoreComponent(value)) + } + +var ItemStack.displayNameAccordingToNbt: Text + get() = get(DataComponentTypes.CUSTOM_NAME) ?: get(DataComponentTypes.ITEM_NAME) ?: item.name + set(value) { + set(DataComponentTypes.CUSTOM_NAME, value) + } + +fun ItemStack.setCustomName(text: Text) { + set(DataComponentTypes.CUSTOM_NAME, text) +} diff --git a/src/main/kotlin/util/mc/SkullItemData.kt b/src/main/kotlin/util/mc/SkullItemData.kt new file mode 100644 index 0000000..0405b65 --- /dev/null +++ b/src/main/kotlin/util/mc/SkullItemData.kt @@ -0,0 +1,88 @@ +@file:UseSerializers(DashlessUUIDSerializer::class, InstantAsLongSerializer::class) + +package moe.nea.firmament.util.mc + +import com.mojang.authlib.GameProfile +import com.mojang.authlib.minecraft.MinecraftProfileTexture +import com.mojang.authlib.properties.Property +import java.util.UUID +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.encodeToString +import net.minecraft.component.DataComponentTypes +import net.minecraft.component.type.ProfileComponent +import net.minecraft.item.ItemStack +import net.minecraft.item.Items +import moe.nea.firmament.Firmament +import moe.nea.firmament.util.Base64Util.padToValidBase64 +import moe.nea.firmament.util.assertTrueOr +import moe.nea.firmament.util.json.DashlessUUIDSerializer +import moe.nea.firmament.util.json.InstantAsLongSerializer + +@Serializable +data class MinecraftProfileTextureKt( + val url: String, + val metadata: Map = mapOf(), +) + +@Serializable +data class MinecraftTexturesPayloadKt( + val textures: Map = mapOf(), + val profileId: UUID? = null, + val profileName: String? = null, + val isPublic: Boolean = true, + val timestamp: Instant = Clock.System.now(), +) + +fun GameProfile.setTextures(textures: MinecraftTexturesPayloadKt) { + val json = Firmament.json.encodeToString(textures) + val encoded = java.util.Base64.getEncoder().encodeToString(json.encodeToByteArray()) + properties.put(propertyTextures, Property(propertyTextures, encoded)) +} + +private val propertyTextures = "textures" + +fun ItemStack.setEncodedSkullOwner(uuid: UUID, encodedData: String) { + assert(this.item == Items.PLAYER_HEAD) + val gameProfile = GameProfile(uuid, "LameGuy123") + gameProfile.properties.put(propertyTextures, Property(propertyTextures, encodedData.padToValidBase64())) + this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) +} + +val zeroUUID = UUID.fromString("d3cb85e2-3075-48a1-b213-a9bfb62360c1") +fun createSkullItem(uuid: UUID, url: String) = ItemStack(Items.PLAYER_HEAD) + .also { it.setSkullOwner(uuid, url) } + +fun ItemStack.setSkullOwner(uuid: UUID, url: String) { + assert(this.item == Items.PLAYER_HEAD) + val gameProfile = GameProfile(uuid, "nea89") + gameProfile.setTextures( + MinecraftTexturesPayloadKt( + textures = mapOf(MinecraftProfileTexture.Type.SKIN to MinecraftProfileTextureKt(url)), + profileId = uuid, + profileName = "nea89", + ) + ) + this.set(DataComponentTypes.PROFILE, ProfileComponent(gameProfile)) +} + + +fun decodeProfileTextureProperty(property: Property): MinecraftTexturesPayloadKt? { + assertTrueOr(property.name == propertyTextures) { return null } + return try { + var encodedF: String = property.value + while (encodedF.length % 4 != 0 && encodedF.last() == '=') { + encodedF = encodedF.substring(0, encodedF.length - 1) + } + val json = java.util.Base64.getDecoder().decode(encodedF).decodeToString() + Firmament.json.decodeFromString(json) + } catch (e: Exception) { + // Malformed profile data + if (Firmament.DEBUG) + e.printStackTrace() + null + } +} + 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 String.ifMatches(regex: Regex, block: (MatchResult) -> T): T? = - regex.matchEntire(this)?.let(block) + regex.matchEntire(this)?.let(block) inline fun 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 = mutableMapOf(), +// val sackTypes: + ) + + object Store : ProfileSpecificDataHolder(serializer(), "Sacks", ::SackContents) + + val items get() = Store.data?.contents ?: mutableMapOf() + val storedRegex = "^Stored: (?$SHORT_NUMBER_FORMAT)/(?$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() + 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 = " (?[+\\-]$SHORT_NUMBER_FORMAT) (?[^(]+) \\(.*\\)".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, - 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, + 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 = 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)) + } + } } -- cgit