diff --git a/core/src/main/kotlin/com/mineinabyss/emojy/EmojyGenerator.kt b/core/src/main/kotlin/com/mineinabyss/emojy/EmojyGenerator.kt index 43d0b12..cdbadbb 100644 --- a/core/src/main/kotlin/com/mineinabyss/emojy/EmojyGenerator.kt +++ b/core/src/main/kotlin/com/mineinabyss/emojy/EmojyGenerator.kt @@ -5,6 +5,7 @@ import com.mineinabyss.emojy.config.Gif import com.mineinabyss.emojy.config.Gifs import com.mineinabyss.idofront.font.Space import com.mineinabyss.idofront.font.Space.Companion.toNumber +import com.mineinabyss.idofront.messaging.broadcastVal import com.mineinabyss.idofront.resourcepacks.ResourcePacks import net.kyori.adventure.key.Key import team.unnamed.creative.ResourcePack @@ -18,6 +19,7 @@ import team.unnamed.creative.texture.Texture import java.awt.image.BufferedImage import java.io.File import javax.imageio.ImageIO +import kotlin.io.path.Path import kotlin.math.ceil import kotlin.math.sqrt @@ -59,6 +61,199 @@ object EmojyGenerator { } Font.font(emojyConfig.spaceFont, spaceProvider).addTo(resourcePack) + generateGifShaderFiles(resourcePack) + + resourcePack.packMeta(48, "") + + File("C:\\Users\\Sivert\\AppData\\Roaming\\ModrinthApp\\profiles\\MineInAbyss - 1.21\\resourcepacks\\pack").deleteRecursively() + MinecraftResourcePackWriter.minecraft().writeToDirectory(File("C:\\Users\\Sivert\\AppData\\Roaming\\ModrinthApp\\profiles\\MineInAbyss - 1.21\\resourcepacks\\pack"), resourcePack) + MinecraftResourcePackWriter.minecraft().writeToZipFile(emojy.plugin.dataFolder.resolve("pack.zip"), resourcePack) } + + private fun generateGifShaderFiles(resourcePack: ResourcePack) { + val fsh = Writable.stringUtf8( + """ + #version 150 + + #moj_import + + uniform sampler2D Sampler0; + uniform vec4 ColorModulator; + uniform float FogStart, FogEnd; + uniform vec4 FogColor; + + in float vertexDistance; + in vec4 vertexColor; + in vec2 texCoord0; + + out vec4 fragColor; + + void main() { + vec4 v = texture(Sampler0, texCoord0) * vertexColor * ColorModulator; + if (v.w == 0 ) { + discard; + } + fragColor = linear_fog(v, vertexDistance, FogStart, FogEnd, FogColor); + } + """.trimIndent() + ) + val vsh = Writable.stringUtf8( + """ + #version 150 + + in vec3 Position; + in vec4 Color; + in vec2 UV0; + in ivec2 UV2; + + uniform sampler2D Sampler0, Sampler2; + uniform mat4 ModelViewMat, ProjMat; + uniform float GameTime; + + out float vertexDistance; + out vec4 vertexColor; + out vec2 texCoord0; + + void main() { + gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.); + vertexDistance = length((ModelViewMat * vec4(Position, 1.)).xyz); + texCoord0 = UV0; + + if ( Color.xyz == vec3( 254 ) / 255.0 ) { + vec2 dimensions = textureSize( Sampler0, 0 ); + vec2 texShift = 1 / dimensions; + + // Just in case the texture is not its own image + // Otherwise we could just fetch the pixel at 0, 0 + ivec2 quadrantUV = ivec2( UV0 * dimensions ); + vec4 quadrant = texelFetch( Sampler0, quadrantUV, 0 ); + vec2 newUV0 = UV0; + if ( quadrant.a == ( 149.0 / 255.0 ) ) { + vec4 infoPix1 = vec4( 0 ); + vec4 infoPix2 = vec4( 0 ); + vertexColor = vec4( 1 ); + if ( quadrant.r == 1.0 / 255.0 ) { + infoPix1 = texelFetch( Sampler0, quadrantUV + ivec2( 1, 0 ), 0 ); + infoPix2 = texelFetch( Sampler0, quadrantUV + ivec2( 0, 1 ), 0 ); + newUV0 = newUV0 + ( quadrant.gb * 255 + 1 ) / dimensions; + } else if ( quadrant.r == 0.0 / 255.0 ) { + infoPix1 = texelFetch( Sampler0, quadrantUV - ivec2( 1, 0 ), 0 ); + infoPix2 = texelFetch( Sampler0, quadrantUV + ivec2( 0, 1 ), 0 ); + newUV0 = newUV0 + ( quadrant.gb * 255 + vec2( -1, 1 ) ) / dimensions; + } else if ( quadrant.r == 3.0 / 255.0 ) { + infoPix1 = texelFetch( Sampler0, quadrantUV - ivec2( 1, 0 ), 0 ); + infoPix2 = texelFetch( Sampler0, quadrantUV - ivec2( 0, 1 ), 0 ); + newUV0 = newUV0 + ( quadrant.gb * 255 - 1 ) / dimensions; + } else if ( quadrant.r == 2.0 / 255.0 ) { + infoPix1 = texelFetch( Sampler0, quadrantUV + ivec2( 1, 0 ), 0 ); + infoPix2 = texelFetch( Sampler0, quadrantUV - ivec2( 0, 1 ), 0 ); + newUV0 = newUV0 + ( quadrant.gb * 255 + vec2( 1, -1 ) ) / dimensions; + } else { + vertexColor = Color * texelFetch( Sampler2, UV2 / 16, 0 ); + return; + } + + // Get timing info + float totalTime = infoPix1.r * 256 + infoPix1.g; + float startTime = infoPix1.b * 256 + infoPix1.a; + float endTime = infoPix2.r * 256 + infoPix2.g; + + float lower = startTime / totalTime; + float upper = endTime / totalTime; + float total = totalTime / 4705.882352941176; + float whole = 0; + float time = modf( GameTime / total, whole ); + + vertexColor = vec4( time >= lower && time < upper ); + + texCoord0 = newUV0; + } else { + vertexColor = Color * texelFetch( Sampler2, UV2 / 16, 0 ); + } + } else if ( Color.xyz == vec3( floor( 254 / 4. ) / 255. ) ) { + // Get rid of shadows + vertexColor = vec4( 0 ); + } else { + vertexColor = Color * texelFetch(Sampler2, UV2 / 16, 0); + } + } + """.trimIndent() + ) + val json = Writable.stringUtf8( + """ + { + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "rendertype_text", + "fragment": "rendertype_text", + "attributes": [ + "Position", + "Color", + "UV0", + "UV2" + ], + "samplers": [ + { + "name": "Sampler0" + }, + { + "name": "Sampler2" + } + ], + "uniforms": [ + { + "name": "ModelViewMat", + "type": "matrix4x4", + "count": 16, + "values": [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] + }, + { + "name": "ProjMat", + "type": "matrix4x4", + "count": 16, + "values": [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] + }, + { + "name": "GameTime", + "type": "float", + "count": 1, + "values": [ 0 ] + }, + { + "name": "ColorModulator", + "type": "float", + "count": 4, + "values": [ 1, 1, 1, 1 ] + }, + { + "name": "FogStart", + "type": "float", + "count": 1, + "values": [ 0 ] + }, + { + "name": "FogEnd", + "type": "float", + "count": 1, + "values": [ 1 ] + }, + { + "name": "FogColor", + "type": "float", + "count": 4, + "values": [ 0, 0, 0, 0 ] + } + ] + } + """.trimIndent() + ) + + resourcePack.unknownFile("assets/minecraft/shaders/core/rendertype_text.json", json) + resourcePack.unknownFile("assets/minecraft/shaders/core/rendertype_text.fsh", fsh) + resourcePack.unknownFile("assets/minecraft/shaders/core/rendertype_text.vsh", vsh) + } } diff --git a/core/src/main/kotlin/com/mineinabyss/emojy/config/Gif.kt b/core/src/main/kotlin/com/mineinabyss/emojy/config/Gif.kt index 7848473..d2f73ff 100644 --- a/core/src/main/kotlin/com/mineinabyss/emojy/config/Gif.kt +++ b/core/src/main/kotlin/com/mineinabyss/emojy/config/Gif.kt @@ -5,6 +5,7 @@ import com.mineinabyss.emojy.EmojyGenerator.gifFolder import com.mineinabyss.emojy.emojy import com.mineinabyss.emojy.emojyConfig import com.mineinabyss.emojy.spaceComponent +import com.mineinabyss.idofront.messaging.* import com.mineinabyss.idofront.serialization.KeySerializer import com.mineinabyss.idofront.textcomponents.miniMsg import com.mineinabyss.idofront.textcomponents.serialize @@ -26,6 +27,7 @@ import team.unnamed.creative.base.Writable import team.unnamed.creative.font.Font import team.unnamed.creative.font.FontProvider import team.unnamed.creative.texture.Texture +import java.awt.AlphaComposite import java.awt.image.BufferedImage import java.io.File import javax.imageio.ImageIO @@ -61,15 +63,15 @@ data class Gif( SHADER, OBFUSCATION } - private fun unicode(index: Int): Char = Character.toChars(PRIVATE_USE_FIRST + index).first() + private fun unicode(index: Int): String = Character.toChars(PRIVATE_USE_FIRST + index).first().toString() private fun unicode(): String { return when (type) { - GifType.SHADER -> (0 until frameCount).joinToString("") { unicode(it).toString() } - .miniMsg().font(font).color(TextColor.fromHexString("#FEFEFE")).serialize() + GifType.SHADER -> Component.text((0 until frameCount).joinToString(unicode(frameCount)) { unicode(it) }) + .font(font).color(TextColor.fromHexString("#FEFEFE")).serialize() - GifType.OBFUSCATION -> unicode(0).toString().miniMsg() - .decorate(TextDecoration.OBFUSCATED).font(font).color(NamedTextColor.WHITE).serialize() + GifType.OBFUSCATION -> Component.text(unicode(0), NamedTextColor.WHITE) + .decorate(TextDecoration.OBFUSCATED).font(font).serialize() } } @@ -92,14 +94,14 @@ data class Gif( fun font() = Font.font(font, fontProvider(), gifAdvance()) private fun gifAdvance() = - FontProvider.space().advance(unicode(frameCount() + 1).toString(), -(height * aspectRatio + 1).roundToInt()) + FontProvider.space().advance(unicode(frameCount), -(ascent * aspectRatio).roundToInt()) .build() private fun fontProvider(): FontProvider { // Construct the `chars` mapping in `["xyz", "xyz", "xyz"]` format val charsGrid = (0 until bitmapRows).map { row -> (0 until bitmapColumns).joinToString("") { col -> - unicode(row + col * bitmapColumns).toString() + unicode((row * bitmapColumns + col).apply { if (id.contains("neco")) println(this) }) } } @@ -125,10 +127,9 @@ data class Gif( runCatching { val gifFolder = gifFolder.resolve(id) gifFolder.deleteRecursively() + GifConverter.splitGif(gifFile, frameCount()) // Keep individual frame creation - createSpritesheet(gifFolder).let { - ImageIO.write(it, "png", gifSpriteSheet) - } // Create the spritesheet based on frames + createSpritesheet(gifFolder) Texture.texture(Key.key("${framePath.asString()}.png"), Writable.file(gifSpriteSheet)).addTo(resourcePack) }.onFailure { @@ -136,31 +137,29 @@ data class Gif( } } - private fun createSpritesheet(gifFolder: File): BufferedImage { + private fun createSpritesheet(gifFolder: File) { // List all frame files in the folder, ensuring they are sorted by filename val frames = gifFolder.listFiles() ?.filter { it.isFile && it.extension == "png" } ?.sortedBy { it.nameWithoutExtension.toIntOrNull() } // Assumes frames are named in order (e.g., 1.png, 2.png) ?: throw IllegalStateException("No frame files found in $gifFolder") - val frameCount = frames.size - // Determine dimensions based on the first frame - val firstFrame = ImageIO.read(frames[0]) - val (frameWidth, frameHeight) = firstFrame.width to firstFrame.height + val (frameWidth, frameHeight) = ImageIO.read(frames.first()).let { it.width to it.height } // Create the spritesheet image val spritesheet = BufferedImage(bitmapColumns * frameWidth, bitmapRows * frameHeight, BufferedImage.TYPE_INT_ARGB) val graphics = spritesheet.createGraphics() + graphics.composite = AlphaComposite.Src // Ensure correct alpha handling frames.forEachIndexed { index, frameFile -> val x = (index % bitmapColumns) * frameWidth val y = (index / bitmapColumns) * frameHeight - val frameImage = ImageIO.read(frameFile) - graphics.drawImage(frameImage, x, y, null) + graphics.drawImage(ImageIO.read(frameFile), x, y, null) } graphics.dispose() - return spritesheet + ImageIO.write(spritesheet, "png", gifSpriteSheet) + } } \ No newline at end of file