WIP: Reforge recipes

This commit is contained in:
Linnea Gräf
2024-12-24 03:58:43 +01:00
parent fbab19b40f
commit e16c60169b
17 changed files with 858 additions and 93 deletions

View File

@@ -24,6 +24,7 @@ import moe.nea.firmament.compat.rei.recipes.SBEssenceUpgradeRecipe
import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe import moe.nea.firmament.compat.rei.recipes.SBForgeRecipe
import moe.nea.firmament.compat.rei.recipes.SBKatRecipe import moe.nea.firmament.compat.rei.recipes.SBKatRecipe
import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe import moe.nea.firmament.compat.rei.recipes.SBMobDropRecipe
import moe.nea.firmament.compat.rei.recipes.SBReforgeRecipe
import moe.nea.firmament.events.HandledScreenPushREIEvent import moe.nea.firmament.events.HandledScreenPushREIEvent
import moe.nea.firmament.features.inventory.CraftingOverlay import moe.nea.firmament.features.inventory.CraftingOverlay
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlayScreen
@@ -78,6 +79,7 @@ class FirmamentReiPlugin : REIClientPlugin {
registry.add(SBForgeRecipe.Category) registry.add(SBForgeRecipe.Category)
registry.add(SBMobDropRecipe.Category) registry.add(SBMobDropRecipe.Category)
registry.add(SBKatRecipe.Category) registry.add(SBKatRecipe.Category)
registry.add(SBReforgeRecipe.Category)
registry.add(SBEssenceUpgradeRecipe.Category) registry.add(SBEssenceUpgradeRecipe.Category)
} }
@@ -90,6 +92,10 @@ class FirmamentReiPlugin : REIClientPlugin {
registry.registerDisplayGenerator( registry.registerDisplayGenerator(
SBCraftingRecipe.Category.catIdentifier, SBCraftingRecipe.Category.catIdentifier,
SkyblockCraftingRecipeDynamicGenerator) SkyblockCraftingRecipeDynamicGenerator)
registry.registerDisplayGenerator(
SBReforgeRecipe.catIdentifier,
SBReforgeRecipe.DynamicGenerator
)
registry.registerDisplayGenerator( registry.registerDisplayGenerator(
SBForgeRecipe.Category.categoryIdentifier, SBForgeRecipe.Category.categoryIdentifier,
SkyblockForgeRecipeDynamicGenerator) SkyblockForgeRecipeDynamicGenerator)

View File

@@ -0,0 +1,147 @@
package moe.nea.firmament.compat.rei.recipes
import java.util.Optional
import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
import me.shedaniel.rei.api.client.gui.Renderer
import me.shedaniel.rei.api.client.gui.widgets.Widget
import me.shedaniel.rei.api.client.gui.widgets.Widgets
import me.shedaniel.rei.api.client.registry.display.DisplayCategory
import me.shedaniel.rei.api.client.registry.display.DynamicDisplayGenerator
import me.shedaniel.rei.api.client.view.ViewSearchBuilder
import me.shedaniel.rei.api.common.category.CategoryIdentifier
import me.shedaniel.rei.api.common.display.Display
import me.shedaniel.rei.api.common.display.DisplaySerializer
import me.shedaniel.rei.api.common.entry.EntryIngredient
import me.shedaniel.rei.api.common.entry.EntryStack
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import moe.nea.firmament.Firmament
import moe.nea.firmament.compat.rei.SBItemEntryDefinition
import moe.nea.firmament.repo.Reforge
import moe.nea.firmament.repo.ReforgeStore
import moe.nea.firmament.repo.RepoItemTypeCache
import moe.nea.firmament.repo.RepoManager
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.skyblock.ItemType
import moe.nea.firmament.util.skyblockId
import moe.nea.firmament.util.tr
class SBReforgeRecipe(
val reforge: Reforge,
val limitToItem: SkyblockId?,
) : Display {
companion object {
val catIdentifier = CategoryIdentifier.of<SBReforgeRecipe>(Firmament.MOD_ID, "reforge_recipe")
}
object Category : DisplayCategory<SBReforgeRecipe> {
override fun getCategoryIdentifier(): CategoryIdentifier<out SBReforgeRecipe> {
return catIdentifier
}
override fun getTitle(): Text {
return tr("firmament.recipecategory.reforge", "Reforge")
}
override fun getIcon(): Renderer {
return SBItemEntryDefinition.getEntry(SkyblockId("REFORGE_ANVIL"))
}
override fun setupDisplay(display: SBReforgeRecipe, bounds: Rectangle): MutableList<Widget> {
val list = mutableListOf<Widget>()
list.add(Widgets.createRecipeBase(bounds))
// TODO: actual layout after christmas, probably
list.add(Widgets.createSlot(Point(bounds.minX + 10, bounds.centerY))
.markInput().entries(display.inputItems))
val stoneSlot = Widgets.createSlot(Point(bounds.minX + 38, bounds.centerY))
.markInput()
if (display.reforgeStone != null)
stoneSlot.entry(display.reforgeStone)
list.add(stoneSlot)
list.add(Widgets.createSlot(Point(bounds.minX + 38 + 18, bounds.centerY))
.markInput().entries(display.outputItems))
return list
}
}
object DynamicGenerator : DynamicDisplayGenerator<SBReforgeRecipe> {
fun getRecipesForSBItemStack(item: SBItemStack): Optional<List<SBReforgeRecipe>> {
val reforgeRecipes = mutableListOf<SBReforgeRecipe>()
for (reforge in ReforgeStore.findEligibleForInternalName(item.skyblockId)) {
reforgeRecipes.add(SBReforgeRecipe(reforge, item.skyblockId))
}
for (reforge in ReforgeStore.findEligibleForItem(item.itemType ?: ItemType.NIL)) {
reforgeRecipes.add(SBReforgeRecipe(reforge, item.skyblockId))
}
if (reforgeRecipes.isEmpty()) return Optional.empty()
return Optional.of(reforgeRecipes)
}
override fun getRecipeFor(entry: EntryStack<*>): Optional<List<SBReforgeRecipe>> {
if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
val item = entry.castValue<SBItemStack>()
return getRecipesForSBItemStack(item)
}
override fun getUsageFor(entry: EntryStack<*>): Optional<List<SBReforgeRecipe>> {
if (entry.type != SBItemEntryDefinition.type) return Optional.empty()
val item = entry.castValue<SBItemStack>()
ReforgeStore.byReforgeStone[item.skyblockId]?.let { stoneReforge ->
return Optional.of(listOf(SBReforgeRecipe(stoneReforge, null)))
}
return getRecipesForSBItemStack(item)
}
override fun generate(builder: ViewSearchBuilder): Optional<List<SBReforgeRecipe>> {
// TODO: check builder.recipesFor and such and optionally return all reforge recipes
return Optional.empty()
}
}
private val eligibleItems =
if (limitToItem != null) listOfNotNull(RepoManager.getNEUItem(limitToItem))
else reforge.eligibleItems.flatMap {
when (it) {
is Reforge.ReforgeEligibilityFilter.AllowsInternalName ->
listOfNotNull(RepoManager.getNEUItem(it.internalName))
is Reforge.ReforgeEligibilityFilter.AllowsItemType ->
ReforgeStore.resolveItemType(it.itemType)
.flatMap {
RepoItemTypeCache.byItemType[it] ?: listOf()
}
is Reforge.ReforgeEligibilityFilter.AllowsVanillaItemType -> {
listOf() // TODO: add filter support for this and potentially rework this to search for the declared item type in repo, instead of remapped item type
}
}
}
private val inputItems = eligibleItems.map { SBItemEntryDefinition.getEntry(it.skyblockId) }
private val outputItems =
inputItems.map { SBItemEntryDefinition.getEntry(it.value.copy(reforge = reforge.reforgeId)) }
private val reforgeStone = reforge.reforgeStone?.let(SBItemEntryDefinition::getEntry)
private val inputEntries =
listOf(EntryIngredient.of(inputItems)) + listOfNotNull(reforgeStone?.let(EntryIngredient::of))
private val outputEntries = listOf(EntryIngredient.of(outputItems))
override fun getInputEntries(): List<EntryIngredient> {
return inputEntries
}
override fun getOutputEntries(): List<EntryIngredient> {
return outputEntries
}
override fun getCategoryIdentifier(): CategoryIdentifier<*> {
return catIdentifier
}
override fun getDisplayLocation(): Optional<Identifier> {
return Optional.empty()
}
override fun getSerializer(): DisplaySerializer<out Display>? {
return null
}
}

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.repo package moe.nea.firmament.repo
import io.github.moulberry.repo.IReloadable import io.github.moulberry.repo.IReloadable
@@ -6,23 +5,22 @@ import io.github.moulberry.repo.NEURepository
import io.github.moulberry.repo.data.NEURecipe import io.github.moulberry.repo.data.NEURecipe
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
class BetterRepoRecipeCache(val essenceRecipeProvider: EssenceRecipeProvider) : IReloadable { class BetterRepoRecipeCache(vararg val extraProviders: ExtraRecipeProvider) : IReloadable {
var usages: Map<SkyblockId, Set<NEURecipe>> = mapOf() var usages: Map<SkyblockId, Set<NEURecipe>> = mapOf()
var recipes: Map<SkyblockId, Set<NEURecipe>> = mapOf() var recipes: Map<SkyblockId, Set<NEURecipe>> = mapOf()
override fun reload(repository: NEURepository) { override fun reload(repository: NEURepository) {
val usages = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>() val usages = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
val recipes = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>() val recipes = mutableMapOf<SkyblockId, MutableSet<NEURecipe>>()
val baseRecipes = repository.items.items.values val baseRecipes = repository.items.items.values
.asSequence() .asSequence()
.flatMap { it.recipes } .flatMap { it.recipes }
val extraRecipes = essenceRecipeProvider.recipes (baseRecipes + extraProviders.flatMap { it.provideExtraRecipes() })
(baseRecipes + extraRecipes) .forEach { recipe ->
.forEach { recipe -> recipe.allInputs.forEach { usages.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) }
recipe.allInputs.forEach { usages.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } recipe.allOutputs.forEach { recipes.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) }
recipe.allOutputs.forEach { recipes.getOrPut(SkyblockId(it.itemId), ::mutableSetOf).add(recipe) } }
} this.usages = usages
this.usages = usages this.recipes = recipes
this.recipes = recipes }
}
} }

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.repo package moe.nea.firmament.repo
import io.github.moulberry.repo.IReloadable import io.github.moulberry.repo.IReloadable
@@ -7,44 +6,46 @@ import io.github.moulberry.repo.data.NEUIngredient
import io.github.moulberry.repo.data.NEURecipe import io.github.moulberry.repo.data.NEURecipe
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
class EssenceRecipeProvider : IReloadable { class EssenceRecipeProvider : IReloadable, ExtraRecipeProvider {
data class EssenceUpgradeRecipe( data class EssenceUpgradeRecipe(
val itemId: SkyblockId, val itemId: SkyblockId,
val starCountAfter: Int, val starCountAfter: Int,
val essenceCost: Int, val essenceCost: Int,
val essenceType: String, // TODO: replace with proper type val essenceType: String, // TODO: replace with proper type
val extraItems: List<NEUIngredient>, val extraItems: List<NEUIngredient>,
) : NEURecipe { ) : NEURecipe {
val essenceIngredient= NEUIngredient.fromString("${essenceType}:$essenceCost") val essenceIngredient = NEUIngredient.fromString("${essenceType}:$essenceCost")
val allUpgradeComponents = listOf(essenceIngredient) + extraItems val allUpgradeComponents = listOf(essenceIngredient) + extraItems
override fun getAllInputs(): Collection<NEUIngredient> { override fun getAllInputs(): Collection<NEUIngredient> {
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) + allUpgradeComponents
} }
override fun getAllOutputs(): Collection<NEUIngredient> { override fun getAllOutputs(): Collection<NEUIngredient> {
return listOf(NEUIngredient.fromString(itemId.neuItem + ":1")) return listOf(NEUIngredient.fromString(itemId.neuItem + ":1"))
} }
} }
var recipes = listOf<EssenceUpgradeRecipe>() var recipes = listOf<EssenceUpgradeRecipe>()
private set private set
override fun reload(repository: NEURepository) { override fun provideExtraRecipes(): Iterable<NEURecipe> = recipes
val recipes = mutableListOf<EssenceUpgradeRecipe>()
for ((neuId, costs) in repository.constants.essenceCost.costs) { override fun reload(repository: NEURepository) {
// TODO: add dungeonization costs. this is in repo, but not in the repo parser. val recipes = mutableListOf<EssenceUpgradeRecipe>()
for ((starCountAfter, essenceCost) in costs.essenceCosts.entries) { for ((neuId, costs) in repository.constants.essenceCost.costs) {
val items = costs.itemCosts[starCountAfter] ?: emptyList() // TODO: add dungeonization costs. this is in repo, but not in the repo parser.
recipes.add( for ((starCountAfter, essenceCost) in costs.essenceCosts.entries) {
EssenceUpgradeRecipe( val items = costs.itemCosts[starCountAfter] ?: emptyList()
SkyblockId(neuId), recipes.add(
starCountAfter, EssenceUpgradeRecipe(
essenceCost, SkyblockId(neuId),
"ESSENCE_" + costs.type.uppercase(), // how flimsy starCountAfter,
items.map { NEUIngredient.fromString(it) })) essenceCost,
} "ESSENCE_" + costs.type.uppercase(), // how flimsy
} items.map { NEUIngredient.fromString(it) }))
this.recipes = recipes }
} }
this.recipes = recipes
}
} }

View File

@@ -0,0 +1,7 @@
package moe.nea.firmament.repo
import io.github.moulberry.repo.data.NEURecipe
interface ExtraRecipeProvider {
fun provideExtraRecipes(): Iterable<NEURecipe>
}

View File

@@ -24,21 +24,26 @@ import net.minecraft.nbt.NbtCompound
import net.minecraft.nbt.NbtElement import net.minecraft.nbt.NbtElement
import net.minecraft.nbt.NbtOps import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.NbtString import net.minecraft.nbt.NbtString
import net.minecraft.text.Style
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.Firmament import moe.nea.firmament.Firmament
import moe.nea.firmament.gui.config.HudMeta import moe.nea.firmament.gui.config.HudMeta
import moe.nea.firmament.gui.config.HudPosition import moe.nea.firmament.gui.config.HudPosition
import moe.nea.firmament.gui.hud.MoulConfigHud import moe.nea.firmament.gui.hud.MoulConfigHud
import moe.nea.firmament.repo.RepoManager.initialize import moe.nea.firmament.repo.RepoManager.initialize
import moe.nea.firmament.util.LegacyFormattingCode
import moe.nea.firmament.util.LegacyTagParser import moe.nea.firmament.util.LegacyTagParser
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.TestUtil import moe.nea.firmament.util.TestUtil
import moe.nea.firmament.util.directLiteralStringContent
import moe.nea.firmament.util.mc.FirmamentDataComponentTypes import moe.nea.firmament.util.mc.FirmamentDataComponentTypes
import moe.nea.firmament.util.mc.appendLore import moe.nea.firmament.util.mc.appendLore
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.mc.modifyLore import moe.nea.firmament.util.mc.modifyLore
import moe.nea.firmament.util.mc.setCustomName import moe.nea.firmament.util.mc.setCustomName
import moe.nea.firmament.util.mc.setSkullOwner import moe.nea.firmament.util.mc.setSkullOwner
import moe.nea.firmament.util.transformEachRecursively
object ItemCache : IReloadable { object ItemCache : IReloadable {
private val cache: MutableMap<String, ItemStack> = ConcurrentHashMap() private val cache: MutableMap<String, ItemStack> = ConcurrentHashMap()
@@ -70,6 +75,7 @@ object ItemCache : IReloadable {
val ItemStack.isBroken val ItemStack.isBroken
get() = get(FirmamentDataComponentTypes.IS_BROKEN) ?: false get() = get(FirmamentDataComponentTypes.IS_BROKEN) ?: false
fun brokenItemStack(neuItem: NEUItem?, idHint: SkyblockId? = null): ItemStack { fun brokenItemStack(neuItem: NEUItem?, idHint: SkyblockId? = null): ItemStack {
return ItemStack(Items.PAINTING).apply { return ItemStack(Items.PAINTING).apply {
setCustomName(Text.literal(neuItem?.displayName ?: idHint?.neuItem ?: "null")) setCustomName(Text.literal(neuItem?.displayName ?: idHint?.neuItem ?: "null"))
@@ -88,6 +94,35 @@ object ItemCache : IReloadable {
} }
} }
fun un189Lore(lore: String): Text {
val base = Text.literal("")
base.setStyle(Style.EMPTY.withItalic(false))
var lastColorCode = Style.EMPTY
var readOffset = 0
while (readOffset < lore.length) {
var nextCode = lore.indexOf('§', readOffset)
if (nextCode < 0) {
nextCode = lore.length
}
val text = lore.substring(readOffset, nextCode)
if (text.isNotEmpty()) {
base.append(Text.literal(text).setStyle(lastColorCode))
}
readOffset = nextCode + 2
if (nextCode + 1 < lore.length) {
val colorCode = lore[nextCode + 1]
val formatting = LegacyFormattingCode.byCode[colorCode.lowercaseChar()] ?: LegacyFormattingCode.RESET
val modernFormatting = formatting.modern
if (modernFormatting.isColor) {
lastColorCode = Style.EMPTY.withColor(modernFormatting)
} else {
lastColorCode = lastColorCode.withFormatting(modernFormatting)
}
}
}
return base
}
private fun NEUItem.asItemStackNow(): ItemStack { private fun NEUItem.asItemStackNow(): ItemStack {
try { try {
val oldItemTag = get10809CompoundTag() val oldItemTag = get10809CompoundTag()
@@ -95,6 +130,7 @@ object ItemCache : IReloadable {
?: return brokenItemStack(this) ?: return brokenItemStack(this)
val itemInstance = val itemInstance =
ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this) ItemStack.fromNbt(MC.defaultRegistries, modernItemTag).getOrNull() ?: return brokenItemStack(this)
itemInstance.loreAccordingToNbt = lore.map { un189Lore(it) }
val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes") val extraAttributes = oldItemTag.getCompound("tag").getCompound("ExtraAttributes")
if (extraAttributes != null) if (extraAttributes != null)
itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes)) itemInstance.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(extraAttributes))
@@ -129,12 +165,13 @@ object ItemCache : IReloadable {
} }
fun Text.applyLoreReplacements(loreReplacements: Map<String, String>): Text { fun Text.applyLoreReplacements(loreReplacements: Map<String, String>): Text {
assert(this.siblings.isEmpty()) return this.transformEachRecursively {
var string = this.string var string = it.directLiteralStringContent ?: return@transformEachRecursively it
loreReplacements.forEach { (find, replace) -> loreReplacements.forEach { (find, replace) ->
string = string.replace("{$find}", replace) string = string.replace("{$find}", replace)
}
Text.literal(string).setStyle(it.style)
} }
return Text.literal(string).styled { this.style }
} }
var job: Job? = null var job: Job? = null

View File

@@ -0,0 +1,146 @@
package moe.nea.firmament.repo
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.serializer
import net.minecraft.item.Item
import net.minecraft.registry.RegistryKey
import net.minecraft.registry.RegistryKeys
import net.minecraft.util.Identifier
import moe.nea.firmament.util.ReforgeId
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.skyblock.ItemType
import moe.nea.firmament.util.skyblock.Rarity
@Serializable
data class Reforge(
val reforgeName: String,
@SerialName("internalName") val reforgeStone: SkyblockId? = null,
val nbtModifier: ReforgeId? = null,
val requiredRarities: List<Rarity>? = null,
val itemTypes: @Serializable(with = ReforgeEligibilityFilter.ItemTypesSerializer::class) List<ReforgeEligibilityFilter>? = null,
val allowOn: List<ReforgeEligibilityFilter>? = null,
val reforgeCosts: RarityMapped<Double>? = null,
val reforgeAbility: RarityMapped<String>? = null,
val reforgeStats: RarityMapped<Map<String, Double>>? = null,
) {
val eligibleItems get() = allowOn ?: itemTypes ?: listOf()
@Serializable(with = ReforgeEligibilityFilter.Serializer::class)
sealed interface ReforgeEligibilityFilter {
object ItemTypesSerializer : KSerializer<List<ReforgeEligibilityFilter>> {
override val descriptor: SerialDescriptor
get() = JsonElement.serializer().descriptor
override fun deserialize(decoder: Decoder): List<ReforgeEligibilityFilter> {
decoder as JsonDecoder
val jsonElement = decoder.decodeJsonElement()
if (jsonElement is JsonPrimitive && jsonElement.isString) {
return jsonElement.content.split("/").map { AllowsItemType(ItemType.ofName(it)) }
}
if (jsonElement is JsonArray) {
return decoder.json.decodeFromJsonElement(serializer<List<ReforgeEligibilityFilter>>(), jsonElement)
}
jsonElement as JsonObject
val filters = mutableListOf<ReforgeEligibilityFilter>()
jsonElement["internalName"]?.let {
decoder.json.decodeFromJsonElement(serializer<List<SkyblockId>>(), it).forEach {
filters.add(AllowsInternalName(it))
}
}
jsonElement["itemId"]?.let {
decoder.json.decodeFromJsonElement(serializer<List<String>>(), it).forEach {
val ident = Identifier.tryParse(it)
if (ident != null)
filters.add(AllowsVanillaItemType(RegistryKey.of(RegistryKeys.ITEM, ident)))
}
}
return filters
}
override fun serialize(encoder: Encoder, value: List<ReforgeEligibilityFilter>) {
TODO("Not yet implemented")
}
}
object Serializer : KSerializer<ReforgeEligibilityFilter> {
override val descriptor: SerialDescriptor
get() = serializer<JsonElement>().descriptor
override fun deserialize(decoder: Decoder): ReforgeEligibilityFilter {
val jsonObject = serializer<JsonObject>().deserialize(decoder)
jsonObject["internalName"]?.let {
return AllowsInternalName(SkyblockId((it as JsonPrimitive).content))
}
jsonObject["itemType"]?.let {
return AllowsItemType(ItemType.ofName((it as JsonPrimitive).content))
}
jsonObject["minecraftId"]?.let {
return AllowsVanillaItemType(RegistryKey.of(RegistryKeys.ITEM,
Identifier.of((it as JsonPrimitive).content)))
}
error("Unknown item type")
}
override fun serialize(encoder: Encoder, value: ReforgeEligibilityFilter) {
TODO("Not yet implemented")
}
}
data class AllowsItemType(val itemType: ItemType) : ReforgeEligibilityFilter
data class AllowsInternalName(val internalName: SkyblockId) : ReforgeEligibilityFilter
data class AllowsVanillaItemType(val minecraftId: RegistryKey<Item>) : ReforgeEligibilityFilter
}
val reforgeId get() = nbtModifier ?: ReforgeId(reforgeName.lowercase())
@Serializable(with = RarityMapped.Serializer::class)
sealed interface RarityMapped<T> {
class Serializer<T>(
val values: KSerializer<T>
) : KSerializer<RarityMapped<T>> {
override val descriptor: SerialDescriptor
get() = JsonElement.serializer().descriptor
val indirect = MapSerializer(Rarity.serializer(), values)
override fun deserialize(decoder: Decoder): RarityMapped<T> {
decoder as JsonDecoder
val element = decoder.decodeJsonElement()
if (element is JsonObject) {
return PerRarity(decoder.json.decodeFromJsonElement(indirect, element))
} else {
return Direct(decoder.json.decodeFromJsonElement(values, element))
}
}
override fun serialize(encoder: Encoder, value: RarityMapped<T>) {
when (value) {
is Direct<T> ->
values.serialize(encoder, value.value)
is PerRarity<T> ->
indirect.serialize(encoder, value.values)
}
}
}
@Serializable
data class Direct<T>(val value: T) : RarityMapped<T>
@Serializable
data class PerRarity<T>(val values: Map<Rarity, T>) : RarityMapped<T>
}
}

View File

@@ -0,0 +1,124 @@
package moe.nea.firmament.repo
import com.google.gson.JsonElement
import com.mojang.serialization.JsonOps
import io.github.moulberry.repo.IReloadable
import io.github.moulberry.repo.NEURepoFile
import io.github.moulberry.repo.NEURepository
import io.github.moulberry.repo.NEURepositoryException
import io.github.moulberry.repo.data.NEURecipe
import kotlinx.serialization.KSerializer
import kotlinx.serialization.serializer
import net.minecraft.item.Item
import net.minecraft.registry.RegistryKey
import moe.nea.firmament.Firmament
import moe.nea.firmament.util.ReforgeId
import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.json.KJsonOps
import moe.nea.firmament.util.skyblock.ItemType
object ReforgeStore : ExtraRecipeProvider, IReloadable {
override fun provideExtraRecipes(): Iterable<NEURecipe> {
return emptyList()
}
var byType: Map<ItemType, List<Reforge>> = mapOf()
var byVanilla: Map<RegistryKey<Item>, List<Reforge>> = mapOf()
var byInternalName: Map<SkyblockId, List<Reforge>> = mapOf()
var modifierLut = mapOf<ReforgeId, Reforge>()
var byReforgeStone = mapOf<SkyblockId, Reforge>()
var allReforges = listOf<Reforge>()
fun findEligibleForItem(itemType: ItemType): List<Reforge> {
return byType[itemType] ?: listOf()
}
fun findEligibleForInternalName(internalName: SkyblockId): List<Reforge> {
return byInternalName[internalName] ?: listOf()
}
//TODO: return byVanillla
override fun reload(repo: NEURepository) {
val basicReforges =
repo.file("constants/reforges.json")
?.kJson(serializer<Map<String, Reforge>>())
?.values ?: emptyList()
val advancedReforges =
repo.file("constants/reforgestones.json")
?.kJson(serializer<Map<String, Reforge>>())
?.values ?: emptyList()
val allReforges = (basicReforges + advancedReforges)
modifierLut = allReforges.associateBy { it.reforgeId }
byReforgeStone = allReforges.filter { it.reforgeStone != null }
.associateBy { it.reforgeStone!! }
val byType = mutableMapOf<ItemType, MutableList<Reforge>>()
val byVanilla = mutableMapOf<RegistryKey<Item>, MutableList<Reforge>>()
val byInternalName = mutableMapOf<SkyblockId, MutableList<Reforge>>()
this.byType = byType
this.byVanilla = byVanilla
this.byInternalName = byInternalName
for (reforge in allReforges) {
for (eligibleItem in reforge.eligibleItems) {
when (eligibleItem) {
is Reforge.ReforgeEligibilityFilter.AllowsInternalName -> {
byInternalName.getOrPut(eligibleItem.internalName, ::mutableListOf).add(reforge)
}
is Reforge.ReforgeEligibilityFilter.AllowsItemType -> {
val actualItemTypes = resolveItemType(eligibleItem.itemType)
for (itemType in actualItemTypes) {
byType.getOrPut(itemType, ::mutableListOf).add(reforge)
}
}
is Reforge.ReforgeEligibilityFilter.AllowsVanillaItemType -> {
byVanilla.getOrPut(eligibleItem.minecraftId, ::mutableListOf).add(reforge)
}
}
}
}
this.allReforges = allReforges
}
fun resolveItemType(itemType: ItemType): List<ItemType> {
if (ItemType.SWORD == itemType) {
return listOf(
ItemType.SWORD,
ItemType.GAUNTLET,
ItemType.LONGSWORD,// TODO: check name
ItemType.FISHING_WEAPON,// TODO: check name
)
}
if (itemType == ItemType.ofName("ARMOR")) {
return listOf(
ItemType.CHESTPLATE,
ItemType.LEGGINGS,
ItemType.HELMET,
ItemType.BOOTS,
)
}
if (itemType == ItemType.EQUIPMENT) {
return listOf(
ItemType.CLOAK,
ItemType.BRACELET,
ItemType.NECKLACE,
ItemType.BELT,
ItemType.GLOVES,
)
}
if (itemType == ItemType.ROD) {
return listOf(ItemType.FISHING_ROD, ItemType.FISHING_WEAPON)
}
return listOf(itemType)
}
fun <T> NEURepoFile.kJson(serializer: KSerializer<T>): T {
val rawJson = json(JsonElement::class.java)
try {
val kJsonElement = JsonOps.INSTANCE.convertTo(KJsonOps.INSTANCE, rawJson)
return Firmament.json.decodeFromJsonElement(serializer, kJsonElement)
} catch (ex: Exception) {
throw NEURepositoryException(path, "Could not decode kotlin JSON element", ex)
}
}
}

View File

@@ -0,0 +1,15 @@
package moe.nea.firmament.repo
import io.github.moulberry.repo.IReloadable
import io.github.moulberry.repo.NEURepository
import io.github.moulberry.repo.data.NEUItem
import moe.nea.firmament.util.skyblock.ItemType
object RepoItemTypeCache : IReloadable {
var byItemType: Map<ItemType?, List<NEUItem>> = mapOf()
override fun reload(repository: NEURepository) {
byItemType = repository.items.items.values.groupBy { ItemType.fromEscapeCodeLore(it.lore.lastOrNull() ?: "") }
}
}

View File

@@ -53,13 +53,16 @@ object RepoManager {
var recentlyFailedToUpdateItemList = false var recentlyFailedToUpdateItemList = false
val essenceRecipeProvider = EssenceRecipeProvider() val essenceRecipeProvider = EssenceRecipeProvider()
val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider) val recipeCache = BetterRepoRecipeCache(essenceRecipeProvider, ReforgeStore)
fun makeNEURepository(path: Path): NEURepository { fun makeNEURepository(path: Path): NEURepository {
return NEURepository.of(path).apply { return NEURepository.of(path).apply {
registerReloadListener(ItemCache) registerReloadListener(ItemCache)
registerReloadListener(RepoItemTypeCache)
registerReloadListener(ExpLadders) registerReloadListener(ExpLadders)
registerReloadListener(ItemNameLookup) registerReloadListener(ItemNameLookup)
registerReloadListener(ReforgeStore)
registerReloadListener(essenceRecipeProvider)
ReloadRegistrationEvent.publish(ReloadRegistrationEvent(this)) ReloadRegistrationEvent.publish(ReloadRegistrationEvent(this))
registerReloadListener { registerReloadListener {
if (TestUtil.isInTest) return@registerReloadListener if (TestUtil.isInTest) return@registerReloadListener
@@ -70,7 +73,6 @@ object RepoManager {
} }
} }
} }
registerReloadListener(essenceRecipeProvider)
registerReloadListener(recipeCache) registerReloadListener(recipeCache)
} }
} }

View File

@@ -14,11 +14,16 @@ import net.minecraft.util.Formatting
import moe.nea.firmament.repo.ItemCache.asItemStack import moe.nea.firmament.repo.ItemCache.asItemStack
import moe.nea.firmament.util.FirmFormatters import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.LegacyFormattingCode import moe.nea.firmament.util.LegacyFormattingCode
import moe.nea.firmament.util.ReforgeId
import moe.nea.firmament.util.SkyblockId import moe.nea.firmament.util.SkyblockId
import moe.nea.firmament.util.getReforgeId
import moe.nea.firmament.util.getUpgradeStars
import moe.nea.firmament.util.mc.appendLore import moe.nea.firmament.util.mc.appendLore
import moe.nea.firmament.util.mc.displayNameAccordingToNbt import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
import moe.nea.firmament.util.petData import moe.nea.firmament.util.petData
import moe.nea.firmament.util.skyBlockId import moe.nea.firmament.util.skyBlockId
import moe.nea.firmament.util.skyblock.ItemType
import moe.nea.firmament.util.skyblockId import moe.nea.firmament.util.skyblockId
import moe.nea.firmament.util.withColor import moe.nea.firmament.util.withColor
@@ -28,8 +33,8 @@ data class SBItemStack constructor(
private var stackSize: Int, private var stackSize: Int,
private var petData: PetData?, private var petData: PetData?,
val extraLore: List<Text> = emptyList(), val extraLore: List<Text> = emptyList(),
// TODO: grab this star data from nbt if possible
val stars: Int = 0, val stars: Int = 0,
val reforge: ReforgeId? = null,
) { ) {
fun getStackSize() = stackSize fun getStackSize() = stackSize
@@ -66,7 +71,9 @@ data class SBItemStack constructor(
skyblockId, skyblockId,
RepoManager.getNEUItem(skyblockId), RepoManager.getNEUItem(skyblockId),
itemStack.count, itemStack.count,
petData = itemStack.petData?.let { PetData.fromHypixel(it) } petData = itemStack.petData?.let { PetData.fromHypixel(it) },
stars = itemStack.getUpgradeStars(),
reforge = itemStack.getReforgeId()
) )
} }
@@ -127,6 +134,15 @@ data class SBItemStack constructor(
} }
private fun appendReforgeStatsToLore(
itemStack: ItemStack,
) {
val rarity = itemStack.rarity
val lore = itemStack.loreAccordingToNbt
}
// TODO: avoid instantiating the item stack here
val itemType: ItemType? get() = ItemType.fromItemStack(asImmutableItemStack())
private var itemStack_: ItemStack? = null private var itemStack_: ItemStack? = null
private val itemStack: ItemStack private val itemStack: ItemStack
@@ -141,6 +157,7 @@ data class SBItemStack constructor(
return@run neuItem.asItemStack(idHint = skyblockId, replacementData) return@run neuItem.asItemStack(idHint = skyblockId, replacementData)
.copyWithCount(stackSize) .copyWithCount(stackSize)
.also { it.appendLore(extraLore) } .also { it.appendLore(extraLore) }
.also { if (reforge != null) it.appendLore(listOf(Text.literal("Reforge: $reforge"))) } // TODO: use this for proper rendering
.also { enhanceStatsByStars(it, stars) } .also { enhanceStatsByStars(it, stars) }
} }
if (itemStack_ == null) if (itemStack_ == null)

View File

@@ -1,35 +1,37 @@
package moe.nea.firmament.util package moe.nea.firmament.util
import net.minecraft.util.Formatting import net.minecraft.util.Formatting
enum class LegacyFormattingCode(val label: String, val char: Char, val index: Int) { enum class LegacyFormattingCode(val label: String, val char: Char, val index: Int) {
BLACK("BLACK", '0', 0), BLACK("BLACK", '0', 0),
DARK_BLUE("DARK_BLUE", '1', 1), DARK_BLUE("DARK_BLUE", '1', 1),
DARK_GREEN("DARK_GREEN", '2', 2), DARK_GREEN("DARK_GREEN", '2', 2),
DARK_AQUA("DARK_AQUA", '3', 3), DARK_AQUA("DARK_AQUA", '3', 3),
DARK_RED("DARK_RED", '4', 4), DARK_RED("DARK_RED", '4', 4),
DARK_PURPLE("DARK_PURPLE", '5', 5), DARK_PURPLE("DARK_PURPLE", '5', 5),
GOLD("GOLD", '6', 6), GOLD("GOLD", '6', 6),
GRAY("GRAY", '7', 7), GRAY("GRAY", '7', 7),
DARK_GRAY("DARK_GRAY", '8', 8), DARK_GRAY("DARK_GRAY", '8', 8),
BLUE("BLUE", '9', 9), BLUE("BLUE", '9', 9),
GREEN("GREEN", 'a', 10), GREEN("GREEN", 'a', 10),
AQUA("AQUA", 'b', 11), AQUA("AQUA", 'b', 11),
RED("RED", 'c', 12), RED("RED", 'c', 12),
LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13), LIGHT_PURPLE("LIGHT_PURPLE", 'd', 13),
YELLOW("YELLOW", 'e', 14), YELLOW("YELLOW", 'e', 14),
WHITE("WHITE", 'f', 15), WHITE("WHITE", 'f', 15),
OBFUSCATED("OBFUSCATED", 'k', -1), OBFUSCATED("OBFUSCATED", 'k', -1),
BOLD("BOLD", 'l', -1), BOLD("BOLD", 'l', -1),
STRIKETHROUGH("STRIKETHROUGH", 'm', -1), STRIKETHROUGH("STRIKETHROUGH", 'm', -1),
UNDERLINE("UNDERLINE", 'n', -1), UNDERLINE("UNDERLINE", 'n', -1),
ITALIC("ITALIC", 'o', -1), ITALIC("ITALIC", 'o', -1),
RESET("RESET", 'r', -1); RESET("RESET", 'r', -1);
val modern = Formatting.byCode(char)!! companion object {
val byCode = entries.associateBy { it.char }
}
val formattingCode = "§$char" val modern = Formatting.byCode(char)!!
val formattingCode = "§$char"
} }

View File

@@ -125,10 +125,26 @@ val ItemStack.skyblockUUID: UUID?
private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo>>("PetInfo") { private val petDataCache = WeakCache.memoize<ItemStack, Optional<HypixelPetInfo>>("PetInfo") {
val jsonString = it.extraAttributes.getString("petInfo") val jsonString = it.extraAttributes.getString("petInfo")
if (jsonString.isNullOrBlank()) return@memoize Optional.empty() if (jsonString.isNullOrBlank()) return@memoize Optional.empty()
ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") { jsonparser.decodeFromString<HypixelPetInfo>(jsonString) } ErrorUtil.catch<HypixelPetInfo?>("Could not decode hypixel pet info") {
jsonparser.decodeFromString<HypixelPetInfo>(jsonString)
}
.or { null }.intoOptional() .or { null }.intoOptional()
} }
fun ItemStack.getUpgradeStars(): Int {
return extraAttributes.getInt("upgrade_level").takeIf { it > 0 }
?: extraAttributes.getInt("dungeon_item_level").takeIf { it > 0 }
?: 0
}
@Serializable
@JvmInline
value class ReforgeId(val id: String)
fun ItemStack.getReforgeId(): ReforgeId? {
return extraAttributes.getString("modifier").takeIf { it.isNotBlank() }?.let(::ReforgeId)
}
val ItemStack.petData: HypixelPetInfo? val ItemStack.petData: HypixelPetInfo?
get() = petDataCache(this).getOrNull() get() = petDataCache(this).getOrNull()

View File

@@ -13,6 +13,13 @@ value class ItemType private constructor(val name: String) {
return ItemType(name) return ItemType(name)
} }
private val obfuscatedRegex = "§[kK].*?(§[0-9a-fA-FrR]|$)".toRegex()
fun fromEscapeCodeLore(lore: String): ItemType? {
return lore.replace(obfuscatedRegex, "").trim().substringAfter(" ", "")
.takeIf { it.isNotEmpty() }
?.let(::ofName)
}
fun fromItemStack(itemStack: ItemStack): ItemType? { fun fromItemStack(itemStack: ItemStack): ItemType? {
if (itemStack.petData != null) if (itemStack.petData != null)
return PET return PET
@@ -26,13 +33,31 @@ value class ItemType private constructor(val name: String) {
if (type.isEmpty()) return null if (type.isEmpty()) return null
return ofName(type) return ofName(type)
} }
return null return itemStack.loreAccordingToNbt.lastOrNull()?.directLiteralStringContent?.let(::fromEscapeCodeLore)
} }
// TODO: some of those are not actual in game item types, but rather ones included in the repository to splat to multiple in game types. codify those somehow
val SWORD = ofName("SWORD") val SWORD = ofName("SWORD")
val DRILL = ofName("DRILL") val DRILL = ofName("DRILL")
val PICKAXE = ofName("PICKAXE") val PICKAXE = ofName("PICKAXE")
val GAUNTLET = ofName("GAUNTLET") val GAUNTLET = ofName("GAUNTLET")
val LONGSWORD = ofName("LONG SWORD")
val EQUIPMENT = ofName("EQUIPMENT")
val FISHING_WEAPON = ofName("FISHING WEAPON")
val CLOAK = ofName("CLOAK")
val BELT = ofName("BELT")
val NECKLACE = ofName("NECKLACE")
val BRACELET = ofName("BRACELET")
val GLOVES = ofName("GLOVES")
val ROD = ofName("ROD")
val FISHING_ROD = ofName("FISHING ROD")
val VACUUM = ofName("VACUUM")
val CHESTPLATE = ofName("CHESTPLATE")
val LEGGINGS = ofName("LEGGINGS")
val HELMET = ofName("HELMET")
val BOOTS = ofName("BOOTS")
val NIL = ofName("__NIL")
/** /**
* This one is not really official (it never shows up in game). * This one is not really official (it never shows up in game).

View File

@@ -1,5 +1,12 @@
package moe.nea.firmament.util.skyblock package moe.nea.firmament.util.skyblock
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.util.StringUtil.words import moe.nea.firmament.util.StringUtil.words
@@ -10,6 +17,7 @@ import moe.nea.firmament.util.unformattedString
typealias RepoRarity = io.github.moulberry.repo.data.Rarity typealias RepoRarity = io.github.moulberry.repo.data.Rarity
@Serializable(with = Rarity.Serializer::class)
enum class Rarity(vararg altNames: String) { enum class Rarity(vararg altNames: String) {
COMMON, COMMON,
UNCOMMON, UNCOMMON,
@@ -24,6 +32,19 @@ enum class Rarity(vararg altNames: String) {
UNKNOWN UNKNOWN
; ;
object Serializer : KSerializer<Rarity> {
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor(Rarity::class.java.name, PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): Rarity {
return valueOf(decoder.decodeString().replace(" ", "_"))
}
override fun serialize(encoder: Encoder, value: Rarity) {
encoder.encodeString(value.name)
}
}
val names = setOf(name) + altNames val names = setOf(name) + altNames
val neuRepoRarity: RepoRarity? = RepoRarity.entries.find { it.name == name } val neuRepoRarity: RepoRarity? = RepoRarity.entries.find { it.name == name }

View File

@@ -0,0 +1,96 @@
{
components: {
"minecraft:attribute_modifiers": {
modifiers: [
],
show_in_tooltip: 0b
},
"minecraft:custom_data": {
ability_scroll: [
"IMPLOSION_SCROLL",
"WITHER_SHIELD_SCROLL",
"SHADOW_WARP_SCROLL"
],
art_of_war_count: 1,
champion_combat_xp: 1.3556020889209766E7d,
donated_museum: 1b,
enchantments: {
champion: 10,
cleave: 5,
critical: 6,
cubism: 5,
ender_slayer: 6,
execute: 5,
experience: 3,
fire_aspect: 2,
first_strike: 4,
giant_killer: 6,
impaling: 3,
lethality: 5,
looting: 4,
luck: 6,
scavenger: 4,
smite: 7,
syphon: 4,
thunderlord: 6,
ultimate_wise: 5,
vampirism: 5,
venomous: 5
},
hot_potato_count: 15,
id: "HYPERION",
modifier: "heroic",
rarity_upgrades: 1,
stats_book: 65934,
timestamp: 1658091600000L,
upgrade_level: 5,
uuid: "a45337aa-9eaa-4e6f-aa27-26a42f8eca95"
},
"minecraft:custom_name": '{"extra":[{"color":"light_purple","text":"Heroic Hyperion "},{"color":"gold","text":"✪✪✪✪✪"}],"italic":false,"text":""}',
"minecraft:enchantment_glint_override": 1b,
"minecraft:hide_additional_tooltip": {
},
"minecraft:lore": [
'{"extra":[{"color":"gray","text":"Gear Score: "},{"color":"light_purple","text":"1145 "},{"color":"dark_gray","text":"(4271)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Damage: "},{"color":"red","text":"+355 "},{"color":"yellow","text":"(+30) "},{"color":"dark_gray","text":"(+1,490.37)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Strength: "},{"color":"red","text":"+250 "},{"color":"yellow","text":"(+30) "},{"color":"gold","text":"[+5] "},{"color":"blue","text":"(+50) "},{"color":"dark_gray","text":"(+1,064.55)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Crit Damage: "},{"color":"red","text":"+70% "},{"color":"dark_gray","text":"(+317.1%)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Bonus Attack Speed: "},{"color":"red","text":"+7% "},{"color":"blue","text":"(+7%) "},{"color":"dark_gray","text":"(+10.5%)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Intelligence: "},{"color":"green","text":"+588 "},{"color":"blue","text":"(+125) "},{"color":"dark_gray","text":"(+2,505.09)"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Ferocity: "},{"color":"green","text":"+33 "},{"color":"dark_gray","text":"(+45)"}],"italic":false,"text":""}',
'{"extra":[" ",{"color":"dark_gray","text":"["},{"color":"dark_gray","text":"✎"},{"color":"dark_gray","text":"] "},{"color":"dark_gray","text":"["},{"color":"dark_gray","text":"⚔"},{"color":"dark_gray","text":"]"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"light_purple","text":""},{"bold":true,"color":"light_purple","text":"Ultimate Wise V"},{"color":"blue","text":", "},{"color":"blue","text":"Champion X"},{"color":"blue","text":", "},{"color":"blue","text":"Cleave V"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Critical VI"},{"color":"blue","text":", "},{"color":"blue","text":"Cubism V"},{"color":"blue","text":", "},{"color":"blue","text":"Ender Slayer VI"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Execute V"},{"color":"blue","text":", "},{"color":"blue","text":"Experience III"},{"color":"blue","text":", "},{"color":"blue","text":"Fire Aspect II"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"First Strike IV"},{"color":"blue","text":", "},{"color":"blue","text":"Giant Killer VI"},{"color":"blue","text":", "},{"color":"blue","text":"Impaling III"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Lethality V"},{"color":"blue","text":", "},{"color":"blue","text":"Looting IV"},{"color":"blue","text":", "},{"color":"blue","text":"Luck VI"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Scavenger IV"},{"color":"blue","text":", "},{"color":"blue","text":"Smite VII"},{"color":"blue","text":", "},{"color":"blue","text":"Syphon IV"}],"italic":false,"text":""}',
'{"extra":[{"color":"blue","text":"Thunderlord VI"},{"color":"blue","text":", "},{"color":"blue","text":"Vampirism V"},{"color":"blue","text":", "},{"color":"blue","text":"Venomous V"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Deals "},{"color":"red","text":"+50% "},{"color":"gray","text":"damage to Withers."}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Grants "},{"color":"red","text":"+1 "},{"color":"red","text":"❁ Damage "},{"color":"gray","text":"and "},{"color":"green","text":"+2 "},{"color":"aqua","text":"✎"}],"italic":false,"text":""}',
'{"extra":[{"color":"aqua","text":"Intelligence "},{"color":"gray","text":"per "},{"color":"red","text":"Catacombs "},{"color":"gray","text":"level."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"green","text":"Scroll Abilities:"}],"italic":false,"text":""}',
'{"extra":[{"color":"gold","text":"Ability: Wither Impact "},{"bold":true,"color":"yellow","text":"RIGHT CLICK"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Teleport "},{"color":"green","text":"10 blocks"},{"color":"gray","text":" ahead of you."}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Then implode dealing "},{"color":"red","text":"21,658 "},{"color":"gray","text":"damage"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"to nearby enemies. Also applies the"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"wither shield scroll ability reducing"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"damage taken and granting an"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"absorption shield for "},{"color":"yellow","text":"5 "},{"color":"gray","text":"seconds."}],"italic":false,"text":""}',
'{"extra":[{"color":"dark_gray","text":"Mana Cost: "},{"color":"dark_aqua","text":"150"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"white","text":"Kills: "},{"color":"gold","text":"65,934"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"dark_gray","text":"* "},{"color":"dark_gray","text":"Co-op Soulbound "},{"bold":true,"color":"dark_gray","text":"*"}],"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"},"",{"bold":false,"extra":[" "],"italic":false,"obfuscated":false,"strikethrough":false,"text":"","underlined":false},{"bold":true,"color":"light_purple","text":"MYTHIC DUNGEON SWORD "},{"bold":true,"color":"light_purple","obfuscated":true,"text":"a"}],"italic":false,"text":""}'
],
"minecraft:unbreakable": {
show_in_tooltip: 0b
}
},
count: 1,
id: "minecraft:iron_sword"
}

View File

@@ -0,0 +1,105 @@
{
components: {
"minecraft:attribute_modifiers": {
modifiers: [
],
show_in_tooltip: 0b
},
"minecraft:custom_data": {
attributes: {
dominance: 1,
experience: 1
},
id: "IMPLOSION_BELT",
timestamp: "12/5/22 5:17 PM",
uuid: "5c04f47e-7c6c-4ced-96b1-b8f83187b0a5"
},
"minecraft:custom_name": '{"extra":[{"color":"dark_purple","text":"Implosion Belt"}],"italic":false,"text":""}',
"minecraft:hide_additional_tooltip": {
},
"minecraft:lore": [
'{"extra":[{"color":"gray","text":"Defense: "},{"color":"green","text":"+70"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"red","text":"Dominance I ✖"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Gain "},{"color":"red","text":"+1.5% "},{"color":"gray","text":"damage when at full health."}],"italic":false,"text":""}',
'{"extra":[{"color":"aqua","text":"Experience I"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Gain "},{"color":"green","text":"+10% "},{"color":"gray","text":"more experience orbs"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"from killing mobs."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gold","text":"Ability: Consolidated "}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Increases all explosion damage dealt by "},{"color":"green","text":"25%"},{"color":"gray","text":"."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"This item can be reforged!"}],"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"dark_purple","text":"EPIC BELT"}],"italic":false,"text":""}'
],
"minecraft:profile": {
id: [I;
-896440193,
-59755884,
-1280665573,
-1297214643
],
properties: [
{
name: "textures",
signature: "",
value: "ewogICJ0aW1lc3RhbXAiIDogMTY0MzYwMjI5OTA2MSwKICAicHJvZmlsZUlkIiA6ICI0ZTMwZjUwZTdiYWU0M2YzYWZkMmE3NDUyY2ViZTI5YyIsCiAgInByb2ZpbGVOYW1lIiA6ICJfdG9tYXRvel8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjFkMmIwMzZkZDY2NGJiOTBjOWQ0NDNjMTk5OGZiNTI2Mzk4YWI0ZGRkZWI3OWI4NDAxYjE2YjlhNGQxMGJhMyIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9"
}
]
}
},
count: 1,
id: "minecraft:player_head"
}{
components: {
"minecraft:attribute_modifiers": {
modifiers: [
],
show_in_tooltip: 0b
},
"minecraft:custom_data": {
attributes: {
dominance: 1,
experience: 1
},
id: "IMPLOSION_BELT",
timestamp: "12/5/22 5:17 PM",
uuid: "5c04f47e-7c6c-4ced-96b1-b8f83187b0a5"
},
"minecraft:custom_name": '{"extra":[{"color":"dark_purple","text":"Implosion Belt"}],"italic":false,"text":""}',
"minecraft:hide_additional_tooltip": {
},
"minecraft:lore": [
'{"extra":[{"color":"gray","text":"Defense: "},{"color":"green","text":"+70"}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"red","text":"Dominance I ✖"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Gain "},{"color":"red","text":"+1.5% "},{"color":"gray","text":"damage when at full health."}],"italic":false,"text":""}',
'{"extra":[{"color":"aqua","text":"Experience I"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"Gain "},{"color":"green","text":"+10% "},{"color":"gray","text":"more experience orbs"}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":"from killing mobs."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gold","text":"Ability: Consolidated "}],"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"gray","text":"Increases all explosion damage dealt by "},{"color":"green","text":"25%"},{"color":"gray","text":"."}],"italic":false,"text":""}',
'{"italic":false,"text":""}',
'{"extra":[{"color":"gray","text":""},{"color":"dark_gray","text":"This item can be reforged!"}],"italic":false,"text":""}',
'{"extra":[{"bold":true,"color":"dark_purple","text":"EPIC BELT"}],"italic":false,"text":""}'
],
"minecraft:profile": {
id: [I;
-896440193,
-59755884,
-1280665573,
-1297214643
],
properties: [
{
name: "textures",
signature: "",
value: "ewogICJ0aW1lc3RhbXAiIDogMTY0MzYwMjI5OTA2MSwKICAicHJvZmlsZUlkIiA6ICI0ZTMwZjUwZTdiYWU0M2YzYWZkMmE3NDUyY2ViZTI5YyIsCiAgInByb2ZpbGVOYW1lIiA6ICJfdG9tYXRvel8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjFkMmIwMzZkZDY2NGJiOTBjOWQ0NDNjMTk5OGZiNTI2Mzk4YWI0ZGRkZWI3OWI4NDAxYjE2YjlhNGQxMGJhMyIsCiAgICAgICJtZXRhZGF0YSIgOiB7CiAgICAgICAgIm1vZGVsIiA6ICJzbGltIgogICAgICB9CiAgICB9CiAgfQp9"
}
]
}
},
count: 1,
id: "minecraft:player_head"
}