Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2364b55
Fix badge link formatting in readme.md
ThatOneDevil Dec 19, 2025
8e55c02
Fix Sinytra connector badge link in README
ThatOneDevil Dec 19, 2025
5ea05cf
Merge branch 'beta'
ThatOneDevil Dec 19, 2025
b87f8ef
Changed the name of the nbt files to include server ip for sorting.
ThatOneDevil Dec 23, 2025
204dcb0
Added 2 new config values, debug and seralizeItemStack for new additi…
ThatOneDevil Dec 23, 2025
5ddb44c
Fixes major bug when hovering over button. closing the inventory and …
ThatOneDevil Dec 23, 2025
dc173a7
Refactor debug logging and command registry; remove unused serializeI…
ThatOneDevil Dec 23, 2025
18375a5
Fixed servername that i forgot to change
ThatOneDevil Dec 23, 2025
d3d3e33
Fixed an error with multi-version compiling due to some import issues
ThatOneDevil Dec 23, 2025
f99d233
# Update 1.6.2 (2025-12-23)
ThatOneDevil Dec 23, 2025
bbde22e
# Update 1.6.2 (2025-12-23)
ThatOneDevil Dec 23, 2025
af63c1a
Ignore specific files in push events for improved workflow efficiency
ThatOneDevil Dec 24, 2025
badac8f
# Update 1.6.3 (2025-12-30)
ThatOneDevil Dec 24, 2025
0304b69
``# Update 1.6.3 (2025-12-30)
ThatOneDevil Dec 24, 2025
d05526a
``# Update 1.6.3 (2025-12-30)
ThatOneDevil Dec 24, 2025
e964e76
``# Update 1.6.3 (2025-12-30)
ThatOneDevil Dec 24, 2025
b7bac5e
# Update 1.6.4 (2025-12-27)
ThatOneDevil Dec 27, 2025
6b05fec
# Update 1.7.0 (2025-12-27)
ThatOneDevil Dec 27, 2025
5fd7dc3
Removed useless override for blur and click whoops
ThatOneDevil Dec 28, 2025
58ae847
Added option to switch between legacy and minimessage format.
ThatOneDevil Dec 28, 2025
dcf2231
Updated readme and image cache.
ThatOneDevil Dec 28, 2025
30d704c
Changed link to github links
ThatOneDevil Dec 28, 2025
3812533
# YoinkGUI v1.7.2
ThatOneDevil Jan 1, 2026
b08bf51
# YoinkGUI v1.7.2
ThatOneDevil Jan 1, 2026
d7de9d2
Ignore beta branch in build workflow
ThatOneDevil Jan 2, 2026
1ae69c2
Update src/client/kotlin/me/thatonedevil/config/YoinkGuiSettings.kt
ThatOneDevil Jan 2, 2026
c053494
Gemni code stuff idk
ThatOneDevil Jan 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ name: build

on:
push:
branches-ignore:
- 'beta'
paths-ignore:
- 'README.md'
- 'LICENSE'
- '.gitignore'

schedule:
- cron: '0 0 * * *' # Runs daily at midnight UTC
- cron: '0 0 * * *'

workflow_dispatch:
inputs:
force_nightly:
Expand Down
83 changes: 45 additions & 38 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
id("me.modmuss50.mod-publish-plugin") version "0.8.4"
}

val modVersion = "1.6.1-hotfix"
val modVersion = "1.7.2"

version = "${modVersion}+${property("mod.mod_version") as String}"
group = property("maven_group") as String
Expand All @@ -21,9 +21,8 @@ base {

repositories {
mavenCentral()
maven {
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/"){
name = "sonatype-oss-snapshots1"
url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")
mavenContent { snapshotsOnly() }
}
maven("https://maven.terraformersmc.com/") {
Expand All @@ -32,14 +31,16 @@ repositories {
maven("https://maven.isxander.dev/releases") {
name = "Xander Maven"
}
maven( "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") {
name = "DevAuth"
}
}

loom {
splitEnvironmentSourceSets()


mods {

create("yoinkgui").project.sourceSets {
sourceSets["main"]
sourceSets["client"]
Expand Down Expand Up @@ -71,9 +72,12 @@ dependencies {
modImplementation("com.terraformersmc:modmenu:${modMenu}")
modImplementation("dev.isxander:yet-another-config-lib:${yacl}")


// kotlin
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")

//devauth
modRuntimeOnly("me.djtheredstoner:DevAuth-fabric:1.2.2")

}

tasks.processResources {
Expand All @@ -95,7 +99,7 @@ tasks.processResources {
}

tasks.withType<JavaCompile>().configureEach {
options.release.set(21)
options.release.set(21)
}

java {
Expand Down Expand Up @@ -126,44 +130,47 @@ val generateTemplates = tasks.register<Copy>("generateTemplates") {
sourceSets.main.configure { java.srcDir(generateTemplates.map { it.outputs }) }

publishMods {
displayName.set("YoinkGUI $cleanVersion for MC $mcVersion")
file.set(tasks.remapJar.get().archiveFile)
changelog.set(
rootProject.file("changelogs/${cleanVersion}.md")
.takeIf { it.exists() }
?.readText()
?: "No changelog provided."
)
type = STABLE
modLoaders.add("fabric")

fun versionList(prop: String) = findProperty(prop)?.toString()
?.split(',')
?.map { it.trim() }
?: emptyList()

modrinth {
projectId.set(property("modrinthId") as String)
accessToken.set(providers.environmentVariable("MODRINTH_API_KEY"))
minecraftVersions.addAll(versionList("pub.modrinthMC"))

requires { slug.set("fabric-api") }
requires { slug.set("fabric-language-kotlin") }
displayName.set("YoinkGUI $cleanVersion for MC $mcVersion")
file.set(tasks.remapJar.get().archiveFile)
changelog.set(
rootProject.file("src/main/resources/changelogs/${cleanVersion}.md")
.takeIf { it.exists() }
?.readText()
?: "No changelog provided."
)

type = STABLE
modLoaders.add("fabric")

fun versionList(prop: String) = findProperty(prop)?.toString()
?.split(',')
?.map { it.trim() }
?: emptyList()

modrinth {
projectId.set(property("modrinthId") as String)
accessToken.set(providers.environmentVariable("MODRINTH_API_KEY"))
minecraftVersions.addAll(versionList("pub.modrinthMC"))

requires { slug.set("fabric-api") }
requires { slug.set("fabric-language-kotlin") }
requires { slug.set("yacl") }
requires { slug.set("modmenu") }
}
}

curseforge {
projectId.set(property("curseforgeId") as String)
accessToken.set(providers.environmentVariable("CURSEFORGE_API_KEY"))
minecraftVersions.addAll(versionList("pub.curseMC"))
curseforge {
projectId.set(property("curseforgeId") as String)
accessToken.set(providers.environmentVariable("CURSEFORGE_API_KEY"))
minecraftVersions.addAll(versionList("pub.curseMC"))

requires { slug.set("fabric-api") }
requires { slug.set("fabric-language-kotlin") }
requires { slug.set("fabric-api") }
requires { slug.set("fabric-language-kotlin") }
requires { slug.set("yacl") }
requires { slug.set("modmenu") }
}

}
}





Binary file added imageCache/configMenuDevOptions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imageCache/dragScreen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imageCache/exampleItem.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imageCache/exampleOutput.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 15 additions & 12 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
[![Download on Modrinth](https://raw.githubusercontent.com/intergrav/devins-badges/c7fd18efdadd1c3f12ae56b49afd834640d2d797/assets/cozy/available/modrinth_vector.svg)](https://modrinth.com/mod/yoinkgui)
[![Download on CurseForge](https://raw.githubusercontent.com/intergrav/devins-badges/c7fd18efdadd1c3f12ae56b49afd834640d2d797/assets/cozy/available/curseforge_vector.svg)](https://www.curseforge.com/minecraft/mc-mods/yoinkgui)
[![fapi-badge](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/requires/fabric-api_vector.svg)](https://modrinth.com/mod/fabric-api)
[![sinytra-connector](https://github.com/Sinytra/.github/blob/main/badges/connector/cozy.svg)](https://modrinth.com/mod/connector)
[![sinytra-connector](https://raw.githubusercontent.com/Sinytra/.github/refs/heads/main/badges/connector/cozy.svg)](https://modrinth.com/mod/connector)

![Build Status](https://github.com/ThatOneDevil/yoinkgui/actions/workflows/build.yml/badge.svg)
[![Build Status](https://github.com/ThatOneDevil/yoinkgui/actions/workflows/build.yml/badge.svg)](https://github.com/ThatOneDevil/yoinkgui)
[![Modrinth Donwloads](https://img.shields.io/modrinth/dt/yoinkgui?color=00AF5C&label=downloads&logo=modrinth)](https://modrinth.com/mod/yoinkgui)
[![CurseForge Downloads](https://cf.way2muchnoise.eu/full_yoinkgui_downloads.svg)](https://www.curseforge.com/minecraft/mc-mods/yoinkgui)
[![Discord](https://img.shields.io/discord/1405856687851704420?color=blue&logo=discord&label=Discord)](https://discord.gg/kcegGvZvpC)
Expand Down Expand Up @@ -36,12 +36,19 @@ Report bugs here:

## ✨ Features

- Press **M** to open the configuration menu and customize your parsing options

- **Full Minecraft formatting code support**:
- **Color codes**
- **Bold**, *Italic*, __Underline__, ~~Strikethrough~~, and obfuscated text
- **Color codes, &c &e**
- **Bold**, *Italic*, __Underline__, ~~Strikethrough~~, and obfuscated text

- **Hex and gradient support** (MiniMessage format):
- Hex colors: `<color:#FF00AA>`
- Gradients: `<gradient:#8968CD:#FFA6CA>`
- Hex colors: `<color:#FF00AA>`
- Gradients: `<gradient:#8968CD:#FFA6CA>`
- Shadow: `<shadow:hex:alpha>`

![ConfigMenu](https://github.com/ThatOneDevil/yoinkgui/blob/master/imageCache/configMenuDevOptions.png?raw=true)
![DragScreen](https://github.com/ThatOneDevil/yoinkgui/blob/master/imageCache/dragScreen.png?raw=true)

---

Expand All @@ -53,10 +60,6 @@ Report bugs here:
4. The output `.txt` file will appear in your `config` folder.
5. The file path is also displayed in chat, as shown below

**Example of the path message:**

![Example of the path message](https://cdn.modrinth.com/data/cached_images/681a4eb24635ac98ae987f4a72a741c27c4c3c87.png)

![Example item with lore](https://cdn.modrinth.com/data/cached_images/acb2558b12d52504936e9d1e1afd8f54a93d04ef.png)
![ExampleItem](https://github.com/ThatOneDevil/yoinkgui/blob/master/imageCache/exampleItem.png?raw=true)

![Parsed Lore](https://cdn.modrinth.com/data/cached_images/c468d843708f68328354a1c17ea48f4d8b4dd297.png)
![ExampleOutput](https://github.com/ThatOneDevil/yoinkgui/blob/master/imageCache/exampleOutput.png?raw=true)
21 changes: 12 additions & 9 deletions src/client/java/me/thatonedevil/mixin/client/ScreenMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.*;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
Expand All @@ -17,7 +18,15 @@ public class ScreenMixin {
@Inject(at = @At("HEAD"), method = "render")
private void render(DrawContext context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) {
MinecraftClient client = MinecraftClient.getInstance();
if (client.player == null) {
if (client.player == null || client.world == null) {
return;
}

if (!(client.currentScreen instanceof InventoryScreen
|| client.currentScreen instanceof GenericContainerScreen
|| client.currentScreen instanceof MerchantScreen
|| client.currentScreen instanceof CreativeInventoryScreen
|| client.currentScreen instanceof ShulkerBoxScreen)) {
return;
}

Expand All @@ -31,18 +40,13 @@ private void render(DrawContext context, int mouseX, int mouseY, float deltaTick

int baseButtonWidth = 160;
int baseButtonHeight = 20;
int baseButtonX = 40;
int baseButtonY = 35;


int parseButtonX = (int) (baseButtonX * scaleFactor);
int parseButtonY = (int) (baseButtonY * scaleFactor);
int parseButtonX = config.getButtonX().get();
int parseButtonY = config.getButtonY().get();
int parseButtonWidth = (int) (baseButtonWidth * scaleFactor);
int parseButtonHeight = (int) (baseButtonHeight * scaleFactor);
String parseButtonText = "Yoink and Parse NBT into file";

// Register HUD rendering event
// Calculate mouse position in UI space
int scaledWidth = client.getWindow().getScaledWidth();
int scaledHeight = client.getWindow().getScaledHeight();
int mouseXUi = (int) (client.mouse.getX() * scaledWidth / client.getWindow().getWidth());
Expand All @@ -51,7 +55,6 @@ private void render(DrawContext context, int mouseX, int mouseY, float deltaTick
YoinkGUIClient.INSTANCE.setParseButtonHovered(mouseXUi >= parseButtonX && mouseXUi <= parseButtonX + parseButtonWidth &&
mouseYUi >= parseButtonY && mouseYUi <= parseButtonY + parseButtonHeight);

// Draw second button (Parse NBT) with appropriate color
int parseBgColor = YoinkGUIClient.INSTANCE.getParseButtonHovered() ? 0xAA444444 : 0xAA000000;
context.fill(parseButtonX, parseButtonY, parseButtonX + parseButtonWidth, parseButtonY + parseButtonHeight, parseBgColor);
context.drawCenteredTextWithShadow(
Expand Down
50 changes: 24 additions & 26 deletions src/client/kotlin/me/thatonedevil/NBTParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import com.google.gson.Gson
import com.google.gson.JsonObject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import me.thatonedevil.utils.Utils.toClickable
import me.thatonedevil.YoinkGUIClient.logger
import me.thatonedevil.config.YoinkGuiSettings
import me.thatonedevil.utils.Utils.toClickCopy
import me.thatonedevil.utils.Utils.toComponent
import me.thatonedevil.YoinkGUI.logger
import me.thatonedevil.utils.Utils
import me.thatonedevil.nbt.ComponentValueRegistry
import me.thatonedevil.utils.LatestErrorLog
import me.thatonedevil.utils.api.UpdateChecker.serverName
import java.io.File
import java.io.FileWriter
import java.time.Duration
Expand All @@ -21,12 +24,8 @@ object NBTParser {

private fun parseTextComponent(obj: JsonObject): String = buildString {
val result = ComponentValueRegistry.process(obj)
if (result.text.isNotEmpty()) {
append(result.text)
if (result.stopPropagation) return@buildString
}

append(obj.get("text")?.asString ?: "")
append(result.text)
if (result.stopPropagation) return@buildString

obj.get("extra")?.asJsonArray?.forEach {
if (it.isJsonObject) append(parseTextComponent(it.asJsonObject))
Expand All @@ -46,11 +45,12 @@ object NBTParser {
val json = gson.fromJson(raw, JsonObject::class.java)
val components = json.getAsJsonObject("components") ?: return@buildString

val hasName = components.has("minecraft:custom_name")
val hasName = components.has("minecraft:custom_name") || components.has("minecraft:item_name")
val hasLore = components.has("minecraft:lore")
if (!hasName && !hasLore) return@buildString

components.get("minecraft:custom_name")?.let { customNameElement ->
val nameElement = components.get("minecraft:custom_name") ?: components.get("minecraft:item_name")
nameElement?.let { customNameElement ->
append("Name: ")
when {
customNameElement.isJsonObject -> append(parseTextComponent(customNameElement.asJsonObject))
Expand All @@ -76,15 +76,14 @@ object NBTParser {


suspend fun saveFormattedNBTToFile(nbtList: List<String>, configDir: File) = withContext(Dispatchers.IO) {
val start = LocalDateTime.now()
val formattedTime = start.format(DateTimeFormatter.ofPattern("MM-dd HH-mm-ss"))
try {
val start = LocalDateTime.now()
val formattedTime = start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"))
val yoinkDir = File(configDir, "yoinkgui").apply { mkdirs() }
val fileName = "formatted_nbt_${formattedTime}.txt"
val yoinkDir = File(configDir, "assets/yoinkgui").apply { mkdirs() }
val fileName = "${serverName}-${formattedTime}.txt"
val file = File(yoinkDir, fileName)

FileWriter(file).use { writer ->
// keep the original index so we can reference the raw NBT correctly later
val contentItems = nbtList.mapIndexedNotNull { i, raw ->
val formatted = parseNewNBTFormat(raw)
if (formatted.isNotBlank()) Pair(i, formatted) else null
Expand All @@ -99,25 +98,24 @@ object NBTParser {

contentItems.forEachIndexed { outIdx, (originalIndex, formatted) ->
writer.write("=== ITEM ${originalIndex + 1} ===\n")
writer.write("Raw NBT: ${nbtList[originalIndex]}\n")
if (YoinkGuiSettings.includeRawNbt.get()) { writer.write("Raw NBT: ${nbtList[originalIndex]}\n") }

writer.write("\n$formatted\n")
if (outIdx < contentItems.lastIndex) writer.write("\n${"=".repeat(50)}\n\n")
}
}

val duration = Duration.between(start, LocalDateTime.now()).toMillis()

try {
Utils.sendChat(
"\n<color:#FFA6CA>Formatted NBT data saved to:".toComponent(),
" <color:#FFA6CA>Parse time: <color:#8968CD>${duration}ms".toComponent(),
" <color:#8968CD>${file.absolutePath} &7&o(Click to copy)\n".toClickable(file.absolutePath)
)
} catch (inner: Exception) {
logger?.error("Error while sending save notification to chat: ${inner.message}", inner)
}
Utils.sendChat(
"\n<color:#FFA6CA>Formatted NBT data saved to:".toComponent(),
" <color:#FFA6CA>Parse time: <color:#8968CD>${duration}ms".toComponent(),
" <color:#8968CD>${file.absolutePath} &7&o(Click to copy)\n".toClickCopy(file.absolutePath)
)

} catch (e: Exception) {
logger?.error("Error saving NBT file: ${e.message}", e)
LatestErrorLog.record(e, "Error saving NBT file")
logger.error("Error saving NBT file: ${e.message}", e)
}
}
}
Loading