feat: Add 1.8.9 item exporter
This commit is contained in:
@@ -26,7 +26,6 @@ import net.fabricmc.loader.api.Version
|
||||
import net.fabricmc.loader.api.metadata.ModMetadata
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.apache.logging.log4j.Logger
|
||||
import org.spongepowered.asm.launch.MixinBootstrap
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -74,10 +73,22 @@ object Firmament {
|
||||
allowTrailingComma = true
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
prettyPrintIndent = "\t"
|
||||
}
|
||||
|
||||
/**
|
||||
* FUCK two space indentation
|
||||
*/
|
||||
val twoSpaceJson = Json(from = json) {
|
||||
prettyPrintIndent = " "
|
||||
}
|
||||
val gson = Gson()
|
||||
val tightJson = Json(from = json) {
|
||||
prettyPrint = false
|
||||
// Reset pretty print indent back to default to prevent getting yelled at by json
|
||||
prettyPrintIndent = " "
|
||||
encodeDefaults = false
|
||||
explicitNulls = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ object PowerUserTools : FirmamentFeature {
|
||||
val copyEntityData by keyBindingWithDefaultUnbound("entity-data")
|
||||
val copyItemStack by keyBindingWithDefaultUnbound("copy-item-stack")
|
||||
val copyTitle by keyBindingWithDefaultUnbound("copy-title")
|
||||
val exportItemStackToRepo by keyBindingWithDefaultUnbound("export-item-stack")
|
||||
}
|
||||
|
||||
override val config
|
||||
@@ -64,14 +65,13 @@ object PowerUserTools : FirmamentFeature {
|
||||
var lastCopiedStack: Pair<ItemStack, Text>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
if (value != null) lastCopiedStackViewTime = true
|
||||
if (value != null) lastCopiedStackViewTime = 2
|
||||
}
|
||||
var lastCopiedStackViewTime = false
|
||||
var lastCopiedStackViewTime = 0
|
||||
|
||||
@Subscribe
|
||||
fun resetLastCopiedStack(event: TickEvent) {
|
||||
if (!lastCopiedStackViewTime) lastCopiedStack = null
|
||||
lastCopiedStackViewTime = false
|
||||
if (lastCopiedStackViewTime-- < 0) lastCopiedStack = null
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -232,7 +232,7 @@ object PowerUserTools : FirmamentFeature {
|
||||
lastCopiedStack = null
|
||||
return
|
||||
}
|
||||
lastCopiedStackViewTime = true
|
||||
lastCopiedStackViewTime = 0
|
||||
it.lines.add(text)
|
||||
}
|
||||
|
||||
|
||||
246
src/main/kotlin/features/debug/itemeditor/ItemExporter.kt
Normal file
246
src/main/kotlin/features/debug/itemeditor/ItemExporter.kt
Normal file
@@ -0,0 +1,246 @@
|
||||
package moe.nea.firmament.features.debug.itemeditor
|
||||
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.io.path.createParentDirectories
|
||||
import kotlin.io.path.relativeTo
|
||||
import kotlin.io.path.writeText
|
||||
import net.minecraft.component.DataComponentTypes
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.nbt.NbtInt
|
||||
import net.minecraft.nbt.NbtString
|
||||
import net.minecraft.text.Text
|
||||
import net.minecraft.util.Unit
|
||||
import moe.nea.firmament.Firmament
|
||||
import moe.nea.firmament.annotations.Subscribe
|
||||
import moe.nea.firmament.events.ClientStartedEvent
|
||||
import moe.nea.firmament.events.HandledScreenKeyPressedEvent
|
||||
import moe.nea.firmament.features.debug.PowerUserTools
|
||||
import moe.nea.firmament.repo.RepoDownloadManager
|
||||
import moe.nea.firmament.util.HypixelPetInfo
|
||||
import moe.nea.firmament.util.LegacyTagWriter.Companion.toLegacyString
|
||||
import moe.nea.firmament.util.StringUtil.words
|
||||
import moe.nea.firmament.util.directLiteralStringContent
|
||||
import moe.nea.firmament.util.extraAttributes
|
||||
import moe.nea.firmament.util.focusedItemStack
|
||||
import moe.nea.firmament.util.getLegacyFormatString
|
||||
import moe.nea.firmament.util.json.toJsonArray
|
||||
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
|
||||
import moe.nea.firmament.util.mc.loreAccordingToNbt
|
||||
import moe.nea.firmament.util.mc.toNbtList
|
||||
import moe.nea.firmament.util.skyBlockId
|
||||
import moe.nea.firmament.util.skyblock.Rarity
|
||||
import moe.nea.firmament.util.tr
|
||||
import moe.nea.firmament.util.transformEachRecursively
|
||||
import moe.nea.firmament.util.unformattedString
|
||||
|
||||
class ItemExporter(var itemStack: ItemStack) {
|
||||
var lore = itemStack.loreAccordingToNbt
|
||||
var name = itemStack.displayNameAccordingToNbt
|
||||
val extraAttribs = itemStack.extraAttributes.copy()
|
||||
val legacyNbt = NbtCompound()
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
fun preprocess() {
|
||||
// TODO: split up preprocess steps into preprocess actions that can be toggled in a ui
|
||||
extraAttribs.remove("timestamp")
|
||||
extraAttribs.remove("uuid")
|
||||
extraAttribs.remove("modifier")
|
||||
extraAttribs.getString("petInfo").ifPresent { petInfoJson ->
|
||||
var petInfo = Firmament.json.decodeFromString<HypixelPetInfo>(petInfoJson)
|
||||
petInfo = petInfo.copy(candyUsed = 0, heldItem = null, exp = 0.0, active = null, uuid = null)
|
||||
extraAttribs.putString("petInfo", Firmament.tightJson.encodeToString(petInfo))
|
||||
}
|
||||
itemStack.skyBlockId?.let {
|
||||
extraAttribs.putString("id", it.neuItem)
|
||||
}
|
||||
trimLore()
|
||||
}
|
||||
|
||||
fun trimLore() {
|
||||
val rarityIdx = lore.indexOfLast {
|
||||
val firstWordInLine = it.unformattedString.words().filter { it.length > 2 }.firstOrNull()
|
||||
firstWordInLine?.let(Rarity::fromString) != null
|
||||
}
|
||||
if (rarityIdx >= 0) {
|
||||
lore = lore.subList(0, rarityIdx + 1)
|
||||
}
|
||||
deleteLineUntilNextSpace { it.startsWith("Held Item: ") }
|
||||
deleteLineUntilNextSpace { it.startsWith("Progress to Level ") }
|
||||
deleteLineUntilNextSpace { it.startsWith("MAX LEVEL") }
|
||||
collapseWhitespaces()
|
||||
|
||||
name = name.transformEachRecursively {
|
||||
var string = it.directLiteralStringContent ?: return@transformEachRecursively it
|
||||
string = string.replace("Lvl \\d+".toRegex(), "Lvl {LVL}")
|
||||
Text.literal(string).setStyle(it.style)
|
||||
}
|
||||
}
|
||||
|
||||
fun collapseWhitespaces() {
|
||||
lore = (listOf(null as Text?) + lore).zipWithNext()
|
||||
.filter { !it.first?.unformattedString.isNullOrBlank() || !it.second?.unformattedString.isNullOrBlank() }
|
||||
.map { it.second!! }
|
||||
}
|
||||
|
||||
fun deleteLineUntilNextSpace(search: (String) -> Boolean) {
|
||||
val idx = lore.indexOfFirst { search(it.unformattedString) }
|
||||
if (idx < 0) return
|
||||
val l = lore.toMutableList()
|
||||
val p = l.subList(idx, l.size)
|
||||
val nextBlank = p.indexOfFirst { it.unformattedString.isEmpty() }
|
||||
if (nextBlank < 0)
|
||||
p.clear()
|
||||
else
|
||||
p.subList(0, nextBlank).clear()
|
||||
lore = l
|
||||
}
|
||||
|
||||
fun processNbt() {
|
||||
// TODO: calculate hideflags
|
||||
legacyNbt.put("HideFlags", NbtInt.of(254))
|
||||
copyUnbreakable()
|
||||
copyExtraAttributes()
|
||||
copyLegacySkullNbt()
|
||||
copyDisplay()
|
||||
copyEnchantments()
|
||||
copyEnchantGlint()
|
||||
// TODO: copyDisplay
|
||||
}
|
||||
|
||||
private fun copyDisplay() {
|
||||
legacyNbt.put("display", NbtCompound().apply {
|
||||
put("Lore", lore.map { NbtString.of(it.getLegacyFormatString(trimmed = true)) }.toNbtList())
|
||||
putString("Name", name.getLegacyFormatString(trimmed = true))
|
||||
})
|
||||
}
|
||||
|
||||
fun exportJson(): JsonElement {
|
||||
preprocess()
|
||||
processNbt()
|
||||
return buildJsonObject {
|
||||
val (itemId, damage) = legacyifyItemStack()
|
||||
put("itemid", itemId)
|
||||
put("displayname", name.getLegacyFormatString(trimmed = true))
|
||||
put("nbttag", legacyNbt.toLegacyString())
|
||||
put("damage", damage)
|
||||
put("lore", lore.map { it.getLegacyFormatString(trimmed = true) }.toJsonArray())
|
||||
val sbId = itemStack.skyBlockId
|
||||
if (sbId == null)
|
||||
warnings.add("Could not find skyblock id")
|
||||
put("internalname", sbId?.neuItem)
|
||||
put("clickcommand", "")
|
||||
put("crafttext", "")
|
||||
put("modver", "Firmament ${Firmament.version.friendlyString}")
|
||||
put("infoType", "")
|
||||
put("info", JsonArray(listOf()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Subscribe
|
||||
fun load(event: ClientStartedEvent) {
|
||||
thread(start = true, name = "ItemExporter Meta Load Thread") {
|
||||
LegacyItemData.itemLut
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onKeyBind(event: HandledScreenKeyPressedEvent) {
|
||||
if (event.matches(PowerUserTools.TConfig.exportItemStackToRepo)) {
|
||||
val itemStack = event.screen.focusedItemStack ?: return
|
||||
val exporter = ItemExporter(itemStack)
|
||||
val json = exporter.exportJson()
|
||||
val jsonFormatted = Firmament.twoSpaceJson.encodeToString(json)
|
||||
val itemFile = RepoDownloadManager.repoSavedLocation.resolve("items")
|
||||
.resolve("${json.jsonObject["internalname"]!!.jsonPrimitive.content}.json")
|
||||
itemFile.createParentDirectories()
|
||||
itemFile.writeText(jsonFormatted)
|
||||
PowerUserTools.lastCopiedStack = Pair(
|
||||
itemStack,
|
||||
tr(
|
||||
"firmament.repoexport.success",
|
||||
"Exported item to ${itemFile.relativeTo(RepoDownloadManager.repoSavedLocation)}${
|
||||
exporter.warnings.joinToString(
|
||||
""
|
||||
) { "\nWarning: $it" }
|
||||
}"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun copyEnchantGlint() {
|
||||
if (itemStack.get(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE) == true) {
|
||||
val ench = legacyNbt.getListOrEmpty("ench")
|
||||
legacyNbt.put("ench", ench)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyUnbreakable() {
|
||||
if (itemStack.get(DataComponentTypes.UNBREAKABLE) == Unit.INSTANCE) {
|
||||
legacyNbt.putBoolean("Unbreakable", true)
|
||||
}
|
||||
}
|
||||
|
||||
fun copyEnchantments() {
|
||||
val enchantments = itemStack.get(DataComponentTypes.ENCHANTMENTS)?.takeIf { !it.isEmpty } ?: return
|
||||
val enchTag = legacyNbt.getListOrEmpty("ench")
|
||||
legacyNbt.put("ench", enchTag)
|
||||
enchantments.enchantmentEntries.forEach { entry ->
|
||||
val id = entry.key.key.get().value
|
||||
val legacyId = LegacyItemData.enchantmentLut[id]
|
||||
if (legacyId == null) {
|
||||
warnings.add("Could not find legacy enchantment id for ${id}")
|
||||
return@forEach
|
||||
}
|
||||
enchTag.add(NbtCompound().apply {
|
||||
putShort("lvl", entry.intValue.toShort())
|
||||
putShort(
|
||||
"id",
|
||||
legacyId.id.toShort()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun copyExtraAttributes() {
|
||||
legacyNbt.put("ExtraAttributes", extraAttribs)
|
||||
}
|
||||
|
||||
fun copyLegacySkullNbt() {
|
||||
val profile = itemStack.get(DataComponentTypes.PROFILE) ?: return
|
||||
legacyNbt.put("SkullOwner", NbtCompound().apply {
|
||||
profile.id.ifPresent {
|
||||
putString("Id", it.toString())
|
||||
}
|
||||
putBoolean("hypixelPopulated", true)
|
||||
put("Properties", NbtCompound().apply {
|
||||
profile.properties().forEach { prop, value ->
|
||||
val list = getListOrEmpty(prop)
|
||||
put(prop, list)
|
||||
list.add(NbtCompound().apply {
|
||||
value.signature?.let {
|
||||
putString("Signature", it)
|
||||
}
|
||||
putString("Value", value.value)
|
||||
putString("Name", value.name)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fun legacyifyItemStack(): LegacyItemData.LegacyItemType {
|
||||
// TODO: add a default here
|
||||
return LegacyItemData.itemLut[itemStack.item]!!
|
||||
}
|
||||
}
|
||||
73
src/main/kotlin/features/debug/itemeditor/LegacyItemData.kt
Normal file
73
src/main/kotlin/features/debug/itemeditor/LegacyItemData.kt
Normal file
@@ -0,0 +1,73 @@
|
||||
package moe.nea.firmament.features.debug.itemeditor
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.util.Identifier
|
||||
import moe.nea.firmament.Firmament
|
||||
import moe.nea.firmament.repo.ItemCache
|
||||
import moe.nea.firmament.util.MC
|
||||
|
||||
/**
|
||||
* Load data based on [prismarine.js' 1.8 item data](https://github.com/PrismarineJS/minecraft-data/blob/master/data/pc/1.8/items.json)
|
||||
*/
|
||||
object LegacyItemData {
|
||||
@Serializable
|
||||
data class ItemData(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val displayName: String,
|
||||
val stackSize: Int,
|
||||
val variations: List<Variation> = listOf()
|
||||
) {
|
||||
val properId = if (name.contains(":")) name else "minecraft:$name"
|
||||
|
||||
fun allVariants() =
|
||||
variations.map { LegacyItemType(properId, it.metadata.toShort()) } + LegacyItemType(properId, 0)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Variation(
|
||||
val metadata: Int, val displayName: String
|
||||
)
|
||||
|
||||
data class LegacyItemType(
|
||||
val name: String,
|
||||
val metadata: Short
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "$name:$metadata"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class EnchantmentData(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val displayName: String,
|
||||
)
|
||||
|
||||
inline fun <reified T : Any> getLegacyData(name: String) =
|
||||
Firmament.tryDecodeJsonFromStream<T>(
|
||||
LegacyItemData::class.java.getResourceAsStream("/legacy_data/$name.json")!!
|
||||
).getOrThrow()
|
||||
|
||||
val enchantmentData = getLegacyData<List<EnchantmentData>>("enchantments")
|
||||
val enchantmentLut = enchantmentData.associateBy { Identifier.ofVanilla(it.name) }
|
||||
|
||||
val itemDat = getLegacyData<List<ItemData>>("items")
|
||||
val itemLut = itemDat.flatMap { item ->
|
||||
item.allVariants().map { legacyItemType ->
|
||||
val nbt = ItemCache.convert189ToModern(NbtCompound().apply {
|
||||
putString("id", legacyItemType.name)
|
||||
putByte("Count", 1)
|
||||
putShort("Damage", legacyItemType.metadata)
|
||||
})!!
|
||||
val stack = ItemStack.fromNbt(MC.defaultRegistries, nbt).getOrNull()
|
||||
?: error("Could not transform ${legacyItemType}")
|
||||
stack.item to legacyItemType
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
}
|
||||
103
src/main/kotlin/util/LegacyTagWriter.kt
Normal file
103
src/main/kotlin/util/LegacyTagWriter.kt
Normal file
@@ -0,0 +1,103 @@
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.minecraft.nbt.AbstractNbtList
|
||||
import net.minecraft.nbt.NbtByte
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.nbt.NbtDouble
|
||||
import net.minecraft.nbt.NbtElement
|
||||
import net.minecraft.nbt.NbtEnd
|
||||
import net.minecraft.nbt.NbtFloat
|
||||
import net.minecraft.nbt.NbtInt
|
||||
import net.minecraft.nbt.NbtLong
|
||||
import net.minecraft.nbt.NbtShort
|
||||
import net.minecraft.nbt.NbtString
|
||||
import moe.nea.firmament.util.mc.SNbtFormatter.Companion.SIMPLE_NAME
|
||||
|
||||
class LegacyTagWriter(val compact: Boolean) {
|
||||
companion object {
|
||||
fun stringify(nbt: NbtElement, compact: Boolean): String {
|
||||
return LegacyTagWriter(compact).also { it.writeElement(nbt) }
|
||||
.stringWriter.toString()
|
||||
}
|
||||
|
||||
fun NbtElement.toLegacyString(pretty: Boolean = false): String {
|
||||
return stringify(this, !pretty)
|
||||
}
|
||||
}
|
||||
|
||||
val stringWriter = StringBuilder()
|
||||
var indent = 0
|
||||
fun newLine() {
|
||||
if (compact) return
|
||||
stringWriter.append('\n')
|
||||
repeat(indent) {
|
||||
stringWriter.append(" ")
|
||||
}
|
||||
}
|
||||
|
||||
fun writeElement(nbt: NbtElement) {
|
||||
when (nbt) {
|
||||
is NbtInt -> stringWriter.append(nbt.value.toString())
|
||||
is NbtString -> stringWriter.append(escapeString(nbt.value))
|
||||
is NbtFloat -> stringWriter.append(nbt.value).append('F')
|
||||
is NbtDouble -> stringWriter.append(nbt.value).append('D')
|
||||
is NbtByte -> stringWriter.append(nbt.value).append('B')
|
||||
is NbtLong -> stringWriter.append(nbt.value).append('L')
|
||||
is NbtShort -> stringWriter.append(nbt.value).append('S')
|
||||
is NbtCompound -> writeCompound(nbt)
|
||||
is NbtEnd -> {}
|
||||
is AbstractNbtList -> writeArray(nbt)
|
||||
}
|
||||
}
|
||||
|
||||
fun writeArray(nbt: AbstractNbtList) {
|
||||
stringWriter.append('[')
|
||||
indent++
|
||||
newLine()
|
||||
nbt.forEachIndexed { index, element ->
|
||||
writeName(index.toString())
|
||||
writeElement(element)
|
||||
if (index != nbt.size() - 1) {
|
||||
stringWriter.append(',')
|
||||
newLine()
|
||||
}
|
||||
}
|
||||
indent--
|
||||
if (nbt.size() != 0)
|
||||
newLine()
|
||||
stringWriter.append(']')
|
||||
}
|
||||
|
||||
fun writeCompound(nbt: NbtCompound) {
|
||||
stringWriter.append('{')
|
||||
indent++
|
||||
newLine()
|
||||
val entries = nbt.entrySet().sortedBy { it.key }
|
||||
entries.forEachIndexed { index, it ->
|
||||
writeName(it.key)
|
||||
writeElement(it.value)
|
||||
if (index != entries.lastIndex) {
|
||||
stringWriter.append(',')
|
||||
newLine()
|
||||
}
|
||||
}
|
||||
indent--
|
||||
if (nbt.size != 0)
|
||||
newLine()
|
||||
stringWriter.append('}')
|
||||
}
|
||||
|
||||
fun escapeString(string: String): String {
|
||||
return JsonPrimitive(string).toString()
|
||||
}
|
||||
|
||||
fun escapeName(key: String): String =
|
||||
if (key.matches(SIMPLE_NAME)) key else escapeString(key)
|
||||
|
||||
fun writeName(key: String) {
|
||||
stringWriter.append(escapeName(key))
|
||||
stringWriter.append(':')
|
||||
if (!compact) stringWriter.append(' ')
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ data class HypixelPetInfo(
|
||||
val exp: Double = 0.0,
|
||||
val candyUsed: Int = 0,
|
||||
val uuid: UUID? = null,
|
||||
val active: Boolean = false,
|
||||
val active: Boolean? = false,
|
||||
val heldItem: String? = null,
|
||||
) {
|
||||
val skyblockId get() = SkyblockId("${type.uppercase()};${tier.ordinal}") // TODO: is this ordinal set up correctly?
|
||||
|
||||
11
src/main/kotlin/util/json/KJsonUtils.kt
Normal file
11
src/main/kotlin/util/json/KJsonUtils.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package moe.nea.firmament.util.json
|
||||
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
fun <T : JsonElement> List<T>.asJsonArray(): JsonArray {
|
||||
return JsonArray(this)
|
||||
}
|
||||
|
||||
fun Iterable<String>.toJsonArray(): JsonArray = map { JsonPrimitive(it) }.asJsonArray()
|
||||
10
src/main/kotlin/util/mc/NbtUtil.kt
Normal file
10
src/main/kotlin/util/mc/NbtUtil.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package moe.nea.firmament.util.mc
|
||||
|
||||
import net.minecraft.nbt.NbtElement
|
||||
import net.minecraft.nbt.NbtList
|
||||
|
||||
fun Iterable<NbtElement>.toNbtList() = NbtList().also {
|
||||
for(element in this) {
|
||||
it.add(element)
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ class SNbtFormatter private constructor() : NbtElementVisitor {
|
||||
keys.forEachIndexed { index, key ->
|
||||
writeIndent()
|
||||
val element = compound[key] ?: error("Key '$key' found but not present in compound: $compound")
|
||||
val escapedName = if (key.matches(SIMPLE_NAME)) key else NbtString.escape(key)
|
||||
val escapedName = escapeName(key)
|
||||
result.append(escapedName).append(": ")
|
||||
element.accept(this)
|
||||
if (keys.size != index + 1) {
|
||||
@@ -134,6 +134,9 @@ class SNbtFormatter private constructor() : NbtElementVisitor {
|
||||
|
||||
fun NbtElement.toPrettyString() = prettify(this)
|
||||
|
||||
private val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex()
|
||||
fun escapeName(key: String): String =
|
||||
if (key.matches(SIMPLE_NAME)) key else NbtString.escape(key)
|
||||
|
||||
val SIMPLE_NAME = "[A-Za-z0-9._+-]+".toRegex()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ fun OrderedText.reconstitute(): MutableText {
|
||||
return base
|
||||
|
||||
}
|
||||
|
||||
fun StringVisitable.reconstitute(): MutableText {
|
||||
val base = Text.literal("")
|
||||
base.setStyle(Style.EMPTY.withItalic(false))
|
||||
@@ -82,15 +83,47 @@ val Text.unformattedString: String
|
||||
|
||||
val Text.directLiteralStringContent: String? get() = (this.content as? PlainTextContent)?.string()
|
||||
|
||||
fun Text.getLegacyFormatString() =
|
||||
fun Text.getLegacyFormatString(trimmed: Boolean = false): String =
|
||||
run {
|
||||
var lastCode = "§r"
|
||||
val sb = StringBuilder()
|
||||
fun appendCode(code: String) {
|
||||
if (code != lastCode || !trimmed) {
|
||||
sb.append(code)
|
||||
lastCode = code
|
||||
}
|
||||
}
|
||||
for (component in iterator()) {
|
||||
sb.append(component.style.color?.toChatFormatting()?.toString() ?: "§r")
|
||||
if (component.directLiteralStringContent.isNullOrEmpty() && component.siblings.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
appendCode(component.style.let { style ->
|
||||
var color = style.color?.toChatFormatting()?.toString() ?: "§r"
|
||||
if (style.isBold)
|
||||
color += LegacyFormattingCode.BOLD.formattingCode
|
||||
if (style.isItalic)
|
||||
color += LegacyFormattingCode.ITALIC.formattingCode
|
||||
if (style.isUnderlined)
|
||||
color += LegacyFormattingCode.UNDERLINE.formattingCode
|
||||
if (style.isObfuscated)
|
||||
color += LegacyFormattingCode.OBFUSCATED.formattingCode
|
||||
if (style.isStrikethrough)
|
||||
color += LegacyFormattingCode.STRIKETHROUGH.formattingCode
|
||||
color
|
||||
})
|
||||
sb.append(component.directLiteralStringContent)
|
||||
sb.append("§r")
|
||||
if (!trimmed)
|
||||
appendCode("§r")
|
||||
}
|
||||
sb.toString()
|
||||
}.also {
|
||||
var it = it
|
||||
if (trimmed) {
|
||||
it = it.removeSuffix("§r")
|
||||
if (it.length == 2 && it.startsWith("§"))
|
||||
it = ""
|
||||
}
|
||||
it
|
||||
}
|
||||
|
||||
private val textColorLUT = Formatting.entries
|
||||
@@ -127,7 +160,7 @@ fun MutableText.darkGrey() = withColor(Formatting.DARK_GRAY)
|
||||
fun MutableText.red() = withColor(Formatting.RED)
|
||||
fun MutableText.white() = withColor(Formatting.WHITE)
|
||||
fun MutableText.bold(): MutableText = styled { it.withBold(true) }
|
||||
fun MutableText.hover(text: Text): MutableText = styled {it.withHoverEvent(HoverEvent.ShowText(text))}
|
||||
fun MutableText.hover(text: Text): MutableText = styled { it.withHoverEvent(HoverEvent.ShowText(text)) }
|
||||
|
||||
|
||||
fun MutableText.clickCommand(command: String): MutableText {
|
||||
|
||||
560
src/main/resources/legacy_data/enchantments.json
Normal file
560
src/main/resources/legacy_data/enchantments.json
Normal file
@@ -0,0 +1,560 @@
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"name": "protection",
|
||||
"displayName": "Protection",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 11,
|
||||
"b": -10
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 11,
|
||||
"b": 1
|
||||
},
|
||||
"exclude": [
|
||||
"blast_protection",
|
||||
"fire_protection",
|
||||
"projectile_protection"
|
||||
],
|
||||
"category": "armor",
|
||||
"weight": 10,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "fire_protection",
|
||||
"displayName": "Fire Protection",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": 2
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 8,
|
||||
"b": 10
|
||||
},
|
||||
"exclude": [
|
||||
"blast_protection",
|
||||
"protection",
|
||||
"projectile_protection"
|
||||
],
|
||||
"category": "armor",
|
||||
"weight": 5,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "feather_falling",
|
||||
"displayName": "Feather Falling",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 6,
|
||||
"b": -1
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 6,
|
||||
"b": 5
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "armor_feet",
|
||||
"weight": 5,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "blast_protection",
|
||||
"displayName": "Blast Protection",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 8,
|
||||
"b": 5
|
||||
},
|
||||
"exclude": [
|
||||
"fire_protection",
|
||||
"protection",
|
||||
"projectile_protection"
|
||||
],
|
||||
"category": "armor",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "projectile_protection",
|
||||
"displayName": "Projectile Protection",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 6,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 6,
|
||||
"b": 3
|
||||
},
|
||||
"exclude": [
|
||||
"protection",
|
||||
"blast_protection",
|
||||
"fire_protection"
|
||||
],
|
||||
"category": "armor",
|
||||
"weight": 5,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "respiration",
|
||||
"displayName": "Respiration",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": 0
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 30
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "armor_head",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "aqua_affinity",
|
||||
"displayName": "Aqua Affinity",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 1
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 41
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "armor_head",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "thorns",
|
||||
"displayName": "Thorns",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 20,
|
||||
"b": -10
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "armor_chest",
|
||||
"weight": 1,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "depth_strider",
|
||||
"displayName": "Depth Strider",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": 0
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 15
|
||||
},
|
||||
"exclude": [
|
||||
"frost_walker"
|
||||
],
|
||||
"category": "armor_feet",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "sharpness",
|
||||
"displayName": "Sharpness",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 11,
|
||||
"b": -10
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 11,
|
||||
"b": 10
|
||||
},
|
||||
"exclude": [
|
||||
"smite",
|
||||
"bane_of_arthropods"
|
||||
],
|
||||
"category": "weapon",
|
||||
"weight": 10,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "smite",
|
||||
"displayName": "Smite",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 8,
|
||||
"b": 17
|
||||
},
|
||||
"exclude": [
|
||||
"sharpness",
|
||||
"bane_of_arthropods"
|
||||
],
|
||||
"category": "weapon",
|
||||
"weight": 5,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "bane_of_arthropods",
|
||||
"displayName": "Bane of Arthropods",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 8,
|
||||
"b": 17
|
||||
},
|
||||
"exclude": [
|
||||
"smite",
|
||||
"sharpness"
|
||||
],
|
||||
"category": "weapon",
|
||||
"weight": 5,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "knockback",
|
||||
"displayName": "Knockback",
|
||||
"maxLevel": 2,
|
||||
"minCost": {
|
||||
"a": 20,
|
||||
"b": -15
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "weapon",
|
||||
"weight": 5,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "fire_aspect",
|
||||
"displayName": "Fire Aspect",
|
||||
"maxLevel": 2,
|
||||
"minCost": {
|
||||
"a": 20,
|
||||
"b": -10
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "weapon",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"name": "looting",
|
||||
"displayName": "Looting",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 9,
|
||||
"b": 6
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "weapon",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"name": "efficiency",
|
||||
"displayName": "Efficiency",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": -9
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "digger",
|
||||
"weight": 10,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"name": "silk_touch",
|
||||
"displayName": "Silk Touch",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 15
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [
|
||||
"fortune"
|
||||
],
|
||||
"category": "digger",
|
||||
"weight": 1,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"name": "unbreaking",
|
||||
"displayName": "Unbreaking",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "breakable",
|
||||
"weight": 5,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"name": "fortune",
|
||||
"displayName": "Fortune",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 9,
|
||||
"b": 6
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [
|
||||
"silk_touch"
|
||||
],
|
||||
"category": "digger",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"name": "power",
|
||||
"displayName": "Power",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": -9
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 6
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "bow",
|
||||
"weight": 10,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"name": "punch",
|
||||
"displayName": "Punch",
|
||||
"maxLevel": 2,
|
||||
"minCost": {
|
||||
"a": 20,
|
||||
"b": -8
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 20,
|
||||
"b": 17
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "bow",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"name": "flame",
|
||||
"displayName": "Flame",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 20
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "bow",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"name": "infinity",
|
||||
"displayName": "Infinity",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 20
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"exclude": [
|
||||
"mending"
|
||||
],
|
||||
"category": "bow",
|
||||
"weight": 1,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"name": "luck_of_the_sea",
|
||||
"displayName": "Luck of the Sea",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 9,
|
||||
"b": 6
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "fishing_rod",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"name": "lure",
|
||||
"displayName": "Lure",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 9,
|
||||
"b": 6
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"exclude": [],
|
||||
"category": "fishing_rod",
|
||||
"weight": 2,
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
}
|
||||
]
|
||||
3733
src/main/resources/legacy_data/items.json
Normal file
3733
src/main/resources/legacy_data/items.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user