diff options
Diffstat (limited to 'src/main/kotlin/moe/nea/firmament')
6 files changed, 145 insertions, 5 deletions
diff --git a/src/main/kotlin/moe/nea/firmament/Firmament.kt b/src/main/kotlin/moe/nea/firmament/Firmament.kt index 2ba54a8..6a6007e 100644 --- a/src/main/kotlin/moe/nea/firmament/Firmament.kt +++ b/src/main/kotlin/moe/nea/firmament/Firmament.kt @@ -60,6 +60,7 @@ object Firmament { val json = Json { prettyPrint = DEBUG + isLenient = true ignoreUnknownKeys = true encodeDefaults = true } diff --git a/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt b/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt index 398042d..52834f3 100644 --- a/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt +++ b/src/main/kotlin/moe/nea/firmament/features/debug/PowerUserTools.kt @@ -6,17 +6,24 @@ package moe.nea.firmament.features.debug +import net.minecraft.block.SkullBlock +import net.minecraft.block.entity.SkullBlockEntity import net.minecraft.item.ItemStack import net.minecraft.text.Text +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.hit.HitResult import moe.nea.firmament.events.CustomItemModelEvent import moe.nea.firmament.events.HandledScreenKeyPressedEvent import moe.nea.firmament.events.ItemTooltipEvent import moe.nea.firmament.events.ScreenOpenEvent import moe.nea.firmament.events.TickEvent +import moe.nea.firmament.events.WorldKeyboardEvent import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.mixins.accessor.AccessorHandledScreen import moe.nea.firmament.util.ClipboardUtils +import moe.nea.firmament.util.MC import moe.nea.firmament.util.skyBlockId object PowerUserTools : FirmamentFeature { @@ -28,6 +35,7 @@ object PowerUserTools : FirmamentFeature { val copyItemId by keyBindingWithDefaultUnbound("copy-item-id") val copyTexturePackId by keyBindingWithDefaultUnbound("copy-texture-pack-id") val copyNbtData by keyBindingWithDefaultUnbound("copy-nbt-data") + val copySkullTexture by keyBindingWithDefaultUnbound("copy-skull-texture") } override val config @@ -55,6 +63,29 @@ object PowerUserTools : FirmamentFeature { lastCopiedStackViewTime = true it.lines.add(text) } + WorldKeyboardEvent.subscribe { + if (it.matches(TConfig.copySkullTexture)) { + val p = MC.camera ?: return@subscribe + val blockHit = p.raycast(20.0, 0.0f, false) ?: return@subscribe + if (blockHit.type != HitResult.Type.BLOCK || blockHit !is BlockHitResult) { + MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail")) + return@subscribe + } + val blockAt = p.world.getBlockState(blockHit.blockPos)?.block + val entity = p.world.getBlockEntity(blockHit.blockPos) + if (blockAt !is SkullBlock || entity !is SkullBlockEntity || entity.owner == null) { + MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail")) + return@subscribe + } + val id = CustomSkyBlockTextures.getSkullTexture(entity.owner!!) + if (id == null) { + MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail")) + } else { + ClipboardUtils.setTextContent(id.toString()) + MC.sendChat(Text.translatable("firmament.tooltip.copied.skull", id.toString())) + } + } + } TickEvent.subscribe { if (!lastCopiedStackViewTime) lastCopiedStack = null diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt index b086811..66c0987 100644 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt @@ -6,11 +6,21 @@ package moe.nea.firmament.features.texturepack +import com.mojang.authlib.GameProfile +import com.mojang.authlib.minecraft.MinecraftProfileTexture +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable +import net.minecraft.block.SkullBlock +import net.minecraft.client.MinecraftClient +import net.minecraft.client.render.RenderLayer +import net.minecraft.client.texture.PlayerSkinProvider import net.minecraft.client.util.ModelIdentifier +import net.minecraft.util.Identifier import moe.nea.firmament.events.CustomItemModelEvent import moe.nea.firmament.events.TickEvent import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig +import moe.nea.firmament.util.IdentityCharacteristics +import moe.nea.firmament.util.item.decodeProfileTextureProperty import moe.nea.firmament.util.skyBlockId object CustomSkyBlockTextures : FirmamentFeature { @@ -19,6 +29,7 @@ object CustomSkyBlockTextures : FirmamentFeature { object TConfig : ManagedConfig(identifier) { val enabled by toggle("enabled") { true } + val skullsEnabled by toggle("skulls-enabled") { true } val cacheDuration by integer("cache-duration", 0, 20) { 1 } } @@ -32,8 +43,50 @@ object CustomSkyBlockTextures : FirmamentFeature { it.overrideModel = ModelIdentifier("firmskyblock", id.identifier.path, "inventory") } TickEvent.subscribe { - if (it.tickCount % TConfig.cacheDuration == 0) + if (it.tickCount % TConfig.cacheDuration == 0) { CustomItemModelEvent.clearCache() + skullTextureCache.clear() + } } } + + private val skullTextureCache = mutableMapOf<IdentityCharacteristics<GameProfile>, Any>() + private val sentinelPresentInvalid = Object() + + val mcUrlRegex = "https?://textures.minecraft.net/texture/([a-fA-F0-9]+)".toRegex() + fun getSkullId(profile: GameProfile): String? { + val textures = profile.properties.get(PlayerSkinProvider.TEXTURES) + val textureProperty = textures.singleOrNull() ?: return null + val texture = decodeProfileTextureProperty(textureProperty) ?: return null + val textureUrl = + texture.textures[MinecraftProfileTexture.Type.SKIN]?.url ?: return null + val mcUrlData = mcUrlRegex.matchEntire(textureUrl) ?: return null + return mcUrlData.groupValues[1] + } + + fun getSkullTexture(profile: GameProfile): Identifier? { + val id = getSkullId(profile) ?: return null + return Identifier("firmskyblock", "textures/placedskull/$id.png") + } + + fun modifySkullTexture( + type: SkullBlock.SkullType?, + profile: GameProfile?, + cir: CallbackInfoReturnable<RenderLayer> + ) { + if (type != SkullBlock.Type.PLAYER) return + if (!TConfig.skullsEnabled) return + if (profile == null) return + val ic = IdentityCharacteristics(profile) + + val n = skullTextureCache.getOrPut(ic) { + val id = getSkullTexture(profile) ?: return@getOrPut sentinelPresentInvalid + if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) { + return@getOrPut sentinelPresentInvalid + } + return@getOrPut id + } + if (n === sentinelPresentInvalid) return + cir.returnValue = RenderLayer.getEntityTranslucent(n as Identifier) + } } diff --git a/src/main/kotlin/moe/nea/firmament/util/IdentityCharacteristics.kt b/src/main/kotlin/moe/nea/firmament/util/IdentityCharacteristics.kt new file mode 100644 index 0000000..e6dad79 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/IdentityCharacteristics.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util + +class IdentityCharacteristics<T>(val value: T) { + override fun equals(other: Any?): Boolean { + return value === other + } + + override fun hashCode(): Int { + return System.identityHashCode(value) + } +} diff --git a/src/main/kotlin/moe/nea/firmament/util/MC.kt b/src/main/kotlin/moe/nea/firmament/util/MC.kt index 78f2eec..0c09306 100644 --- a/src/main/kotlin/moe/nea/firmament/util/MC.kt +++ b/src/main/kotlin/moe/nea/firmament/util/MC.kt @@ -7,21 +7,44 @@ package moe.nea.firmament.util import io.github.moulberry.repo.data.Coordinate +import java.util.concurrent.ConcurrentLinkedQueue import net.minecraft.client.MinecraftClient import net.minecraft.client.gui.screen.ingame.HandledScreen +import net.minecraft.text.Text import net.minecraft.util.math.BlockPos +import moe.nea.firmament.events.TickEvent object MC { + + private val messageQueue = ConcurrentLinkedQueue<Text>() + + init { + TickEvent.subscribe { + while (true) { + inGameHud.chatHud.addMessage(messageQueue.poll() ?: break) + } + } + } + + fun sendChat(text: Text) { + if (instance.isOnThread) + inGameHud.chatHud.addMessage(text) + else + messageQueue.add(text) + } + fun sendCommand(command: String) { player?.networkHandler?.sendCommand(command) } + inline val instance get() = MinecraftClient.getInstance() inline val keyboard get() = MinecraftClient.getInstance().keyboard inline val textureManager get() = MinecraftClient.getInstance().textureManager inline val inGameHud get() = MinecraftClient.getInstance().inGameHud inline val font get() = MinecraftClient.getInstance().textRenderer inline val soundManager get() = MinecraftClient.getInstance().soundManager inline val player get() = MinecraftClient.getInstance().player + inline val camera get() = MinecraftClient.getInstance().cameraEntity inline val world get() = MinecraftClient.getInstance().world inline var screen get() = MinecraftClient.getInstance().currentScreen diff --git a/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt b/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt index 5b440b1..4d4d386 100644 --- a/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt +++ b/src/main/kotlin/moe/nea/firmament/util/item/SkullItemData.kt @@ -11,14 +11,16 @@ 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 java.util.* import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers +import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import net.minecraft.client.texture.PlayerSkinProvider import moe.nea.firmament.Firmament +import moe.nea.firmament.util.assertTrueOr import moe.nea.firmament.util.json.DashlessUUIDSerializer import moe.nea.firmament.util.json.InstantAsLongSerializer @@ -30,9 +32,9 @@ data class MinecraftProfileTextureKt( @Serializable data class MinecraftTexturesPayloadKt( - val textures: Map<MinecraftProfileTexture.Type, MinecraftProfileTextureKt>, - val profileId: UUID, - val profileName: String, + val textures: Map<MinecraftProfileTexture.Type, MinecraftProfileTextureKt> = mapOf(), + val profileId: UUID? = null, + val profileName: String? = null, val isPublic: Boolean = true, val timestamp: Instant = Clock.System.now(), ) @@ -43,3 +45,16 @@ fun GameProfile.setTextures(textures: MinecraftTexturesPayloadKt) { properties.put(PlayerSkinProvider.TEXTURES, Property(PlayerSkinProvider.TEXTURES, encoded)) } +fun decodeProfileTextureProperty(property: Property): MinecraftTexturesPayloadKt? { + assertTrueOr(property.name == PlayerSkinProvider.TEXTURES) { return null } + try { + val json = java.util.Base64.getDecoder().decode(property.value).decodeToString() + return Firmament.json.decodeFromString<MinecraftTexturesPayloadKt>(json) + } catch (e: Exception) { + // Malformed profile data + if (Firmament.DEBUG) + e.printStackTrace() + return null + } +} + |