diff options
Diffstat (limited to 'src/main/kotlin/moe/nea/notenoughupdates')
7 files changed, 501 insertions, 0 deletions
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt new file mode 100644 index 0000000..79b8819 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt @@ -0,0 +1,18 @@ +package moe.nea.notenoughupdates + +import io.github.moulberry.repo.NEURepository +import net.fabricmc.api.ModInitializer +import java.nio.file.Path + +object NotEnoughUpdates : ModInitializer { + val DATA_DIR = Path.of(".notenoughupdates") + + const val MOD_ID = "notenoughupdates" + + val neuRepo = NEURepository.of(Path.of("NotEnoughUpdates-REPO")).also { + it.reload() + } + + override fun onInitialize() { + } +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt b/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt new file mode 100644 index 0000000..cc4b0f1 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt @@ -0,0 +1,38 @@ +package moe.nea.notenoughupdates.rei + +import io.github.moulberry.repo.data.NEUItem +import me.shedaniel.rei.api.client.plugins.REIClientPlugin +import me.shedaniel.rei.api.client.registry.entry.EntryRegistry +import me.shedaniel.rei.api.common.entry.EntryStack +import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry +import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes +import moe.nea.notenoughupdates.NotEnoughUpdates.neuRepo +import moe.nea.notenoughupdates.repo.ItemCache.asItemStack +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.ItemStack + + +class NEUReiPlugin : REIClientPlugin { + + companion object { + + fun EntryStack<NEUItem>.asItemEntry(): EntryStack<ItemStack> { + return EntryStack.of(VanillaEntryTypes.ITEM, value.asItemStack()) + } + + + val SKYBLOCK_ITEM_TYPE_ID = ResourceLocation("notenoughupdates", "skyblockitems") + } + + override fun registerEntryTypes(registry: EntryTypeRegistry) { + registry.register(SKYBLOCK_ITEM_TYPE_ID, SBItemEntryDefinition) + } + + + override fun registerEntries(registry: EntryRegistry) { + neuRepo.items.items.values.forEach { + if (!it.isVanilla) + registry.addEntry(EntryStack.of(SBItemEntryDefinition, it)) + } + } +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/rei/SBItemEntryDefinition.kt b/src/main/kotlin/moe/nea/notenoughupdates/rei/SBItemEntryDefinition.kt new file mode 100644 index 0000000..6726b4f --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/rei/SBItemEntryDefinition.kt @@ -0,0 +1,98 @@ +package moe.nea.notenoughupdates.rei + +import com.mojang.blaze3d.vertex.PoseStack +import io.github.moulberry.repo.data.NEUItem +import me.shedaniel.math.Point +import me.shedaniel.math.Rectangle +import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer +import me.shedaniel.rei.api.client.gui.widgets.Tooltip +import me.shedaniel.rei.api.client.gui.widgets.TooltipContext +import me.shedaniel.rei.api.common.entry.EntrySerializer +import me.shedaniel.rei.api.common.entry.EntryStack +import me.shedaniel.rei.api.common.entry.comparison.ComparisonContext +import me.shedaniel.rei.api.common.entry.type.EntryDefinition +import me.shedaniel.rei.api.common.entry.type.EntryType +import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes +import moe.nea.notenoughupdates.rei.NEUReiPlugin.Companion.asItemEntry +import moe.nea.notenoughupdates.repo.ItemCache.asItemStack +import moe.nea.notenoughupdates.repo.ItemCache.getResourceLocation +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.tags.TagKey +import net.minecraft.world.item.ItemStack +import java.util.stream.Stream + +object SBItemEntryDefinition : EntryDefinition<NEUItem> { + override fun equals(o1: NEUItem?, o2: NEUItem?, context: ComparisonContext?): Boolean { + return o1 == o2 + } + + override fun cheatsAs(entry: EntryStack<NEUItem>?, value: NEUItem?): ItemStack? { + return value?.asItemStack() + } + + override fun getValueType(): Class<NEUItem> = NEUItem::class.java + override fun getType(): EntryType<NEUItem> = + EntryType.deferred(NEUReiPlugin.SKYBLOCK_ITEM_TYPE_ID) + + override fun getRenderer(): EntryRenderer<NEUItem> = object : EntryRenderer<NEUItem> { + override fun render( + entry: EntryStack<NEUItem>, + matrices: PoseStack, + bounds: Rectangle, + mouseX: Int, + mouseY: Int, + delta: Float + ) { + VanillaEntryTypes.ITEM.definition.renderer + .render( + entry.asItemEntry(), + matrices, bounds, mouseX, mouseY, delta + ) + } + + override fun getTooltip(entry: EntryStack<NEUItem>, tooltipContext: TooltipContext): Tooltip? { + return VanillaEntryTypes.ITEM.definition.renderer + .getTooltip(entry.asItemEntry(), tooltipContext) + } + + } + + override fun getSerializer(): EntrySerializer<NEUItem>? { + return null + } + + override fun getTagsFor(entry: EntryStack<NEUItem>?, value: NEUItem?): Stream<out TagKey<*>> { + return Stream.empty() + } + + override fun asFormattedText(entry: EntryStack<NEUItem>, value: NEUItem): Component { + return VanillaEntryTypes.ITEM.definition.asFormattedText(entry.asItemEntry(), value.asItemStack()) + } + + override fun hash(entry: EntryStack<NEUItem>, value: NEUItem, context: ComparisonContext): Long { + return value.skyblockItemId.hashCode().toLong() + } + + override fun wildcard(entry: EntryStack<NEUItem>, value: NEUItem): NEUItem { + return value + } + + override fun normalize(entry: EntryStack<NEUItem>, value: NEUItem): NEUItem { + return value + } + + override fun copy(entry: EntryStack<NEUItem>?, value: NEUItem): NEUItem { + return value + } + + override fun isEmpty(entry: EntryStack<NEUItem>?, value: NEUItem?): Boolean { + return false + } + + override fun getIdentifier(entry: EntryStack<NEUItem>?, value: NEUItem): ResourceLocation { + return value.getResourceLocation() + } + + +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt new file mode 100644 index 0000000..aa93fec --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt @@ -0,0 +1,75 @@ +package moe.nea.notenoughupdates.repo + +import com.mojang.serialization.Dynamic +import io.github.moulberry.repo.IReloadable +import io.github.moulberry.repo.NEURepository +import io.github.moulberry.repo.data.NEUItem +import moe.nea.notenoughupdates.util.LegacyTagParser +import moe.nea.notenoughupdates.util.appendLore +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import net.minecraft.util.datafix.DataFixers +import net.minecraft.util.datafix.fixes.References +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import java.util.concurrent.ConcurrentHashMap + +object ItemCache : IReloadable { + val cache: MutableMap<String, ItemStack> = ConcurrentHashMap() + val df = DataFixers.getDataFixer() + var isFlawless = true + + private fun NEUItem.get10809CompoundTag(): CompoundTag = CompoundTag().apply { + put("tag", LegacyTagParser.parse(nbttag)) + putString("id", minecraftItemId) + putByte("Count", 1) + putShort("Damage", damage.toShort()) + } + + private fun CompoundTag.transformFrom10809ToModern(): CompoundTag? = + try { + df.update( + References.ITEM_STACK, + Dynamic(NbtOps.INSTANCE, this), + -1, + 2975 + ).value as CompoundTag + } catch (e: Exception) { + e.printStackTrace() + isFlawless = false + null + } + + private fun NEUItem.asItemStackNow(): ItemStack { + val oldItemTag = get10809CompoundTag() + val modernItemTag = oldItemTag.transformFrom10809ToModern() + ?: return ItemStack(Items.PAINTING).apply { + setHoverName(Component.literal(this@asItemStackNow.displayName)) + appendLore(listOf(Component.literal("Exception rendering item: $skyblockItemId"))) + } + val itemInstance = ItemStack.of(modernItemTag) + if (itemInstance.tag?.contains("Enchantments") == true) { + itemInstance.enchantmentTags.add(CompoundTag()) + } + return itemInstance + } + + fun NEUItem.asItemStack(): ItemStack { + var s = cache[this.skyblockItemId] + if (s == null) { + s = asItemStackNow() + cache[this.skyblockItemId] = s + } + return s + } + + fun NEUItem.getResourceLocation() = + ResourceLocation("skyblockitem", skyblockItemId.lowercase().replace(";", "__")) + + + override fun reload(repository: NEURepository) { + cache.clear() + } +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt new file mode 100644 index 0000000..47b2878 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt @@ -0,0 +1,17 @@ +package moe.nea.notenoughupdates.repo + +import moe.nea.notenoughupdates.NotEnoughUpdates + +object RepoDownloadManager { + + val repoSavedLocation = NotEnoughUpdates.DATA_DIR.resolve("repo-extracted") + val repoMetadataLocation = NotEnoughUpdates.DATA_DIR.resolve("loaded-repo.json") + + data class RepoMetadata( + var latestCommit: String, + var user: String, + var repository: String, + var branch: String, + ) + +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/ItemUtil.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/ItemUtil.kt new file mode 100644 index 0000000..d5b8881 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/ItemUtil.kt @@ -0,0 +1,23 @@ +package moe.nea.notenoughupdates.util + +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.StringTag +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack + +fun ItemStack.appendLore(args: List<Component>) { + val compoundTag = getOrCreateTagElement("display") + val loreList = compoundTag.getOrCreateList("Lore", StringTag.TAG_STRING) + for (arg in args) { + loreList.add(StringTag.valueOf(Component.Serializer.toJson(arg))) + } +} + +fun CompoundTag.getOrCreateList(label: String, tag: Byte): ListTag = getList(label, tag.toInt()).also { + put(label, it) +} + +fun CompoundTag.getOrCreateCompoundTag(label: String): CompoundTag = getCompound(label).also { + put(label, it) +} diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/LegacyTagParser.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/LegacyTagParser.kt new file mode 100644 index 0000000..a4ec7e1 --- /dev/null +++ b/src/main/kotlin/moe/nea/notenoughupdates/util/LegacyTagParser.kt @@ -0,0 +1,232 @@ +package moe.nea.notenoughupdates.util + +import net.minecraft.nbt.* +import java.util.* + +class LegacyTagParser private constructor(string: String) { + data class TagParsingException(val baseString: String, val offset: Int, val mes0: String) : + Exception("$mes0 at $offset in `$baseString`.") + + class StringRacer(val backing: String) { + var idx = 0 + val stack = Stack<Int>() + + fun pushState() { + stack.push(idx) + } + + fun popState() { + idx = stack.pop() + } + + fun discardState() { + stack.pop() + } + + fun peek(count: Int): String { + return backing.substring(minOf(idx, backing.length), minOf(idx + count, backing.length)) + } + + fun finished(): Boolean { + return peek(1).isEmpty() + } + + fun peekReq(count: Int): String? { + val p = peek(count) + if (p.length != count) + return null + return p + } + + fun consumeCountReq(count: Int): String? { + val p = peekReq(count) + if (p != null) + idx += count + return p + } + + fun tryConsume(string: String): Boolean { + val p = peek(string.length) + if (p != string) + return false + idx += p.length + return true + } + + fun consumeWhile(shouldConsumeThisString: (String) -> Boolean): String { + var lastString: String = "" + while (true) { + val nextString = lastString + peek(1) + if (!shouldConsumeThisString(nextString)) { + return lastString + } + idx++ + lastString = nextString + } + } + + fun expect(search: String, errorMessage: String) { + if (!tryConsume(search)) + error(errorMessage) + } + + fun error(errorMessage: String): Nothing { + throw TagParsingException(backing, idx, errorMessage) + } + + } + + val racer = StringRacer(string) + val baseTag = parseTag() + + companion object { + val digitRange = '0'..'9' + fun parse(string: String): CompoundTag { + return LegacyTagParser(string).baseTag + } + } + + fun skipWhitespace() { + racer.consumeWhile { Character.isWhitespace(it.last()) } // Only check last since other chars are always checked before. + } + + fun parseTag(): CompoundTag { + skipWhitespace() + racer.expect("{", "Expected '{’ at start of tag") + skipWhitespace() + val tag = CompoundTag() + while (!racer.tryConsume("}")) { + skipWhitespace() + val lhs = parseIdentifier() + skipWhitespace() + racer.expect(":", "Expected ':' after identifier in tag") + skipWhitespace() + val rhs = parseAny() + tag.put(lhs, rhs) + racer.tryConsume(",") + skipWhitespace() + } + return tag + } + + private fun parseAny(): Tag { + skipWhitespace() + val nextChar = racer.peekReq(1) ?: racer.error("Expected new object, found EOF") + return when { + nextChar == "{" -> parseTag() + nextChar == "[" -> parseList() + nextChar == "\"" -> parseStringTag() + nextChar.first() in (digitRange) -> parseNumericTag() + else -> racer.error("Unexpected token found. Expected start of new element") + } + } + + fun parseList(): ListTag { + skipWhitespace() + racer.expect("[", "Expected '[' at start of tag") + skipWhitespace() + val list = ListTag() + while (!racer.tryConsume("]")) { + skipWhitespace() + racer.pushState() + val lhs = racer.consumeWhile { it.all { it in digitRange } } + skipWhitespace() + if (!racer.tryConsume(":") || lhs.isEmpty()) { // No prefixed 0: + racer.popState() + list.add(parseAny()) // Reparse our number (or not a number) as actual tag + } else { + racer.discardState() + skipWhitespace() + list.add(parseAny()) // Ignore prefix indexes. They should not be generated out of order by any vanilla implementation (which is what NEU should export). Instead append where it appears in order. + } + skipWhitespace() + racer.tryConsume(",") + } + return list + } + + fun parseQuotedString(): String { + skipWhitespace() + racer.expect("\"", "Expected '\"' at string start") + val sb = StringBuilder() + while (true) { + when (val peek = racer.consumeCountReq(1)) { + "\"" -> break + "\\" -> { + val escaped = racer.consumeCountReq(1) ?: racer.error("Unfinished backslash escape") + if (escaped != "\"" && escaped != "\\") { + // Surprisingly i couldn't find unicode escapes to be generated by the original minecraft 1.8.9 implementation + racer.idx-- + racer.error("Invalid backslash escape '$escaped'") + } + sb.append(escaped) + } + null -> racer.error("Unfinished string") + else -> { + sb.append(peek) + } + } + } + return sb.toString() + } + + fun parseStringTag(): StringTag { + return StringTag.valueOf(parseQuotedString()) + } + + object Patterns { + val DOUBLE = "([-+]?[0-9]*\\.?[0-9]+)[d|D]".toRegex() + val FLOAT = "([-+]?[0-9]*\\.?[0-9]+)[f|F]".toRegex() + val BYTE = "([-+]?[0-9]+)[b|B]".toRegex() + val LONG = "([-+]?[0-9]+)[l|L]".toRegex() + val SHORT = "([-+]?[0-9]+)[s|S]".toRegex() + val INTEGER = "([-+]?[0-9]+)".toRegex() + val DOUBLE_UNTYPED = "([-+]?[0-9]*\\.?[0-9]+)".toRegex() + val ROUGH_PATTERN = "[-+]?[0-9]*\\.?[0-9]+[dDbBfFlLsS]?".toRegex() + } + + fun parseNumericTag(): NumericTag { + skipWhitespace() + val textForm = racer.consumeWhile { Patterns.ROUGH_PATTERN.matchEntire(it) != null } + if (textForm.isEmpty()) { + racer.error("Expected numeric tag (starting with either -, +, . or a digit") + } + val doubleMatch = Patterns.DOUBLE.matchEntire(textForm) ?: Patterns.DOUBLE_UNTYPED.matchEntire(textForm) + if (doubleMatch != null) { + return DoubleTag.valueOf(doubleMatch.groups[1]!!.value.toDouble()) + } + val floatMatch = Patterns.FLOAT.matchEntire(textForm) + if (floatMatch != null) { + return FloatTag.valueOf(floatMatch.groups[1]!!.value.toFloat()) + } + val byteMatch = Patterns.BYTE.matchEntire(textForm) + if (byteMatch != null) { + return ByteTag.valueOf(byteMatch.groups[1]!!.value.toByte()) + } + val longMatch = Patterns.LONG.matchEntire(textForm) + if (longMatch != null) { + return LongTag.valueOf(longMatch.groups[1]!!.value.toLong()) + } + val shortMatch = Patterns.SHORT.matchEntire(textForm) + if (shortMatch != null) { + return ShortTag.valueOf(shortMatch.groups[1]!!.value.toShort()) + } + val integerMatch = Patterns.INTEGER.matchEntire(textForm) + if (integerMatch != null) { + return IntTag.valueOf(integerMatch.groups[1]!!.value.toInt()) + } + throw IllegalStateException("Could not properly parse numeric tag '$textForm', despite passing rough verification. This is a bug in the LegacyTagParser") + } + + private fun parseIdentifier(): String { + skipWhitespace() + if (racer.peek(1) == "\"") { + return parseQuotedString() + } + return racer.consumeWhile { + val x = it.last() + x != ':' && !Character.isWhitespace(x) + } + } + +} |