diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PainterRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PainterRecipes.kt index 3a283b803..f43737eca 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PainterRecipes.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/PainterRecipes.kt @@ -10,6 +10,7 @@ import net.minecraft.world.item.crafting.Ingredient import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.stream import ru.dbotthepony.mc.otm.datagen.modLocation +import ru.dbotthepony.mc.otm.recipe.PainterArmorDyeRecipe import ru.dbotthepony.mc.otm.recipe.PainterRecipe import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry @@ -283,4 +284,9 @@ fun addPainterRecipes(consumer: RecipeOutput) { striped(consumer, "stairs", MRegistry.TRITANIUM_STRIPED_STAIRS.itemsWithColor, MRegistry.TRITANIUM_STAIRS.items) striped(consumer, "walls", MRegistry.TRITANIUM_STRIPED_WALL.itemsWithColor, MRegistry.TRITANIUM_WALL.items) striped(consumer, "slabs", MRegistry.TRITANIUM_STRIPED_SLAB.itemsWithColor, MRegistry.TRITANIUM_SLAB.items) + + for (color in DyeColor.entries) { + consumer.accept(PainterArmorDyeRecipe(mapOf(color to 1)).toFinished(modLocation("painter/armor_dye_" + color.getName().lowercase()))) + } + consumer.accept(PainterArmorDyeRecipe(mapOf(null to 15)).toFinished(modLocation("painter/armor_clear_dye"))) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt index 30bd9e6b2..e77d5bd07 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/PainterScreen.kt @@ -32,6 +32,7 @@ import ru.dbotthepony.mc.otm.core.map import ru.dbotthepony.mc.otm.core.math.RGBAColor import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu +import ru.dbotthepony.mc.otm.recipe.PainterRecipe class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { private inner class Bar(parent: EditablePanel<*>, val dye: DyeColor?) : EditablePanel(this@PainterScreen, parent, width = 5f) { @@ -130,8 +131,19 @@ class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) : buttons.forEach { it.remove() } buttons.clear() - for (recipe in menu.possibleRecipes.sortedWith(CreativeMenuItemComparator.map { it.value.output.item })) { - object : LargeRectangleButtonPanel(this@PainterScreen, canvas.canvas, icon = ItemStackIcon(recipe.value.output, 14f, 14f).fixed()) { + val recipes = menu.possibleRecipes + .filter { !it.value.ingredients.isEmpty() } + .sortedWith(CreativeMenuItemComparator.map { it.value.getOutput(menu.inputContainer).item }) + .plus( + menu.possibleRecipes + .filter { it.value.ingredients.isEmpty() } + .sortedBy { it.id } + ) + + for (recipe in recipes) { + val recipeOutput = recipe.value.getOutput(menu.inputContainer) + + object : LargeRectangleButtonPanel(this@PainterScreen, canvas.canvas, icon = ItemStackIcon(recipeOutput, 14f, 14f).fixed()) { init { buttons.add(this) dockRight = 1f @@ -143,7 +155,7 @@ class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) : set(value) {} override fun innerRenderTooltips(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { - val list = getTooltipFromItem(minecraft!!, recipe.value.output) + val list = getTooltipFromItem(minecraft!!, recipeOutput) recipe.value.dyes.forEach { val (dye, amount) = it diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt index 94298f266..697bf2071 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt @@ -3,8 +3,7 @@ package ru.dbotthepony.mc.otm.menu.decorative import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.DyeColor -import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.* import net.minecraft.world.item.crafting.RecipeHolder import net.minecraft.world.level.material.Fluids import net.minecraftforge.common.capabilities.ForgeCapabilities @@ -17,19 +16,14 @@ import ru.dbotthepony.mc.otm.core.ISubscriptable import ru.dbotthepony.mc.otm.core.addAll import ru.dbotthepony.mc.otm.core.collect.SupplierMap import ru.dbotthepony.mc.otm.core.collect.filter -import ru.dbotthepony.mc.otm.core.collect.find import ru.dbotthepony.mc.otm.core.collect.maybe -import ru.dbotthepony.mc.otm.core.ifPresentK import ru.dbotthepony.mc.otm.core.isNotEmpty -import ru.dbotthepony.mc.otm.core.map -import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator import ru.dbotthepony.mc.otm.core.util.ResourceLocationValueCodec -import ru.dbotthepony.mc.otm.isClientThread import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatterySlot import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput -import ru.dbotthepony.mc.otm.recipe.PainterRecipe +import ru.dbotthepony.mc.otm.recipe.AbstractPainterRecipe import ru.dbotthepony.mc.otm.registry.MMenus import ru.dbotthepony.mc.otm.registry.MRecipes import java.util.* @@ -48,7 +42,7 @@ class PainterMenu( val inputContainer = MatteryContainer(::rescan, 1) val outputContainer = MatteryContainer(1) - private var lastRecipe: RecipeHolder? = null + private var lastRecipe: RecipeHolder? = null var selectedRecipe by mSynchronizer.Field(null, ResourceLocationValueCodec.nullable).also { it.addListener { rescan() } } val isBulk = BooleanInputWithFeedback(this, tile?.let { it::isBulk }) @@ -59,6 +53,10 @@ class PainterMenu( val inputSlot = object : MatterySlot(inputContainer, 0) { override fun mayPlace(itemStack: ItemStack): Boolean { + if (!itemStack.isEmpty && itemStack.item is DyeableArmorItem) { + return super.mayPlace(itemStack) + } + return super.mayPlace(itemStack) && inventory.player.level().recipeManager.byType(MRecipes.PAINTER).values.any { it.value.input.test(itemStack) } } } @@ -126,18 +124,24 @@ class PainterMenu( } val listeners = ISubscriptable.Impl() - val possibleRecipes = ArrayList>() + val possibleRecipes = ArrayList>() private fun rescan() { possibleRecipes.clear() possibleRecipes.addAll(inventory.player.level().recipeManager.byType(MRecipes.PAINTER).values.iterator().filter { it.value.input.test(inputContainer[0]) }) + + if (!inputContainer[0].isEmpty && inputContainer[0].item is DyeableArmorItem) { + possibleRecipes.addAll(inventory.player.level().recipeManager.byType(MRecipes.PAINTER_ARMOR_DYE).values) + } + listeners.accept(Unit) if (tile !is PainterBlockEntity) return if (inputContainer.isEmpty || selectedRecipe == null) { outputContainer.clearContent() } else { - val recipe = inventory.player.level().recipeManager.byType(MRecipes.PAINTER)[selectedRecipe] +// val recipe = inventory.player.level().recipeManager.byType(MRecipes.PAINTER)[selectedRecipe] + val recipe = inventory.player.level().recipeManager.byKey(selectedRecipe).get() as RecipeHolder? if (recipe == null || !recipe.value.canCraft(dyeStoredDirect) || !recipe.value.matches(inputContainer, inventory.player.level())) { outputContainer.clearContent() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt index e72cc284e..a93aa55e3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.recipe -import com.google.common.collect.ImmutableMap import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder import it.unimi.dsi.fastutil.objects.Object2IntArrayMap @@ -12,8 +11,7 @@ import net.minecraft.data.recipes.FinishedRecipe import net.minecraft.resources.ResourceLocation import net.minecraft.util.StringRepresentable import net.minecraft.world.Container -import net.minecraft.world.item.DyeColor -import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.* import net.minecraft.world.item.crafting.Ingredient import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.item.crafting.RecipeSerializer @@ -29,24 +27,15 @@ import ru.dbotthepony.mc.otm.data.PredicatedCodecList import ru.dbotthepony.mc.otm.data.minRange import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRecipes -import java.util.EnumMap import java.util.function.Predicate -import java.util.stream.Collector -import java.util.stream.Collectors -class PainterRecipe( - val input: Ingredient, - val output: ItemStack, +abstract class AbstractPainterRecipe( dyes: Map ) : Recipe { - constructor( - input: Ingredient, - output: ItemStack, - dyes: Set - ) : this(input, output, Object2IntArrayMap().also { map -> dyes.forEach { map[it] = 1 } }) - val dyes: Object2IntMap = Object2IntMaps.unmodifiable(Object2IntArrayMap(dyes)) + override fun getToastSymbol(): ItemStack = ItemStack(MItems.PAINTER) + fun canCraft(storedDyes: Map): Boolean { if (isIncomplete) return false if (dyes.isEmpty() || dyes.values.none { it > 0 }) return true @@ -63,6 +52,52 @@ class PainterRecipe( return true } + override fun canCraftInDimensions(p_43999_: Int, p_44000_: Int): Boolean = true + + override fun isSpecial(): Boolean = true + + open fun getOutput(container: Container): ItemStack { + if (container.isEmpty) return ItemStack.EMPTY + + return container[0].copy() + } + + enum class DyeColorWrapper(private val refName: String, val key: DyeColor?) : StringRepresentable { + WHITE("white", DyeColor.WHITE), + ORANGE("orange", DyeColor.ORANGE), + MAGENTA("magenta", DyeColor.MAGENTA), + LIGHT_BLUE("light_blue", DyeColor.LIGHT_BLUE), + YELLOW("yellow", DyeColor.YELLOW), + LIME("lime", DyeColor.LIME), + PINK("pink", DyeColor.PINK), + GRAY("gray", DyeColor.GRAY), + LIGHT_GRAY("light_gray", DyeColor.LIGHT_GRAY), + CYAN("cyan", DyeColor.CYAN), + PURPLE("purple", DyeColor.PURPLE), + BLUE("blue", DyeColor.BLUE), + BROWN("brown", DyeColor.BROWN), + GREEN("green", DyeColor.GREEN), + RED("red", DyeColor.RED), + BLACK("black", DyeColor.BLACK), + WATER("water", null); + + override fun getSerializedName(): String { + return refName + } + } +} + +class PainterRecipe( + val input: Ingredient, + val output: ItemStack, + dyes: Map +) : AbstractPainterRecipe(dyes) { + constructor( + input: Ingredient, + output: ItemStack, + dyes: Set + ) : this(input, output, Object2IntArrayMap().also { map -> dyes.forEach { map[it] = 1 } }) + override fun matches(p_44002_: Container, p_44003_: Level): Boolean { if (isIncomplete) return false return input.test(p_44002_[0]) @@ -90,14 +125,6 @@ class PainterRecipe( } } - override fun canCraftInDimensions(p_43999_: Int, p_44000_: Int): Boolean { - return true - } - - override fun isSpecial(): Boolean { - return true - } - override fun getResultItem(p_267052_: RegistryAccess): ItemStack { return output } @@ -110,36 +137,12 @@ class PainterRecipe( return MRecipes.PAINTER } - override fun getToastSymbol(): ItemStack { - return ItemStack(MItems.PAINTER) - } - fun toFinished(id: ResourceLocation): FinishedRecipe { return SERIALIZER.toFinished(this, id) } - private enum class DyeColorWrapper(val refName: String, val key: DyeColor?) : StringRepresentable { - WHITE("white", DyeColor.WHITE), - ORANGE("orange", DyeColor.ORANGE), - MAGENTA("magenta", DyeColor.MAGENTA), - LIGHT_BLUE("light_blue", DyeColor.LIGHT_BLUE), - YELLOW("yellow", DyeColor.YELLOW), - LIME("lime", DyeColor.LIME), - PINK("pink", DyeColor.PINK), - GRAY("gray", DyeColor.GRAY), - LIGHT_GRAY("light_gray", DyeColor.LIGHT_GRAY), - CYAN("cyan", DyeColor.CYAN), - PURPLE("purple", DyeColor.PURPLE), - BLUE("blue", DyeColor.BLUE), - BROWN("brown", DyeColor.BROWN), - GREEN("green", DyeColor.GREEN), - RED("red", DyeColor.RED), - BLACK("black", DyeColor.BLACK), - WATER("water", null); - - override fun getSerializedName(): String { - return refName - } + override fun getOutput(container: Container): ItemStack { + return this.output } companion object { @@ -160,3 +163,75 @@ class PainterRecipe( } } } +class PainterArmorDyeRecipe( + dyes: Map +) : AbstractPainterRecipe(dyes) { + constructor( + dyes: Set + ) : this(Object2IntArrayMap().also { map -> dyes.forEach { map[it] = 1 } }) + + override fun matches(contaier: Container, level: Level): Boolean { + if (this.isIncomplete) return false + val stack = contaier[0] + + return (!stack.isEmpty && stack.item is DyeableArmorItem) + } + + override fun assemble(container: Container, registry: RegistryAccess): ItemStack { + var output = container[0].copy() + + dyes.forEach { entry -> + if (entry.key == null) { + (output.item as DyeableLeatherItem).clearColor(output) + } else { + output = DyeableLeatherItem.dyeArmor(output.copy(), listOf(DyeItem.byColor(entry.key!!))) + } + } + + return output + } + + override fun getResultItem(registry: RegistryAccess): ItemStack = ItemStack.EMPTY + + override fun getSerializer(): RecipeSerializer<*> = SERIALIZER + + override fun getType(): RecipeType<*> = MRecipes.PAINTER_ARMOR_DYE + + override fun isIncomplete(): Boolean = dyes.isEmpty() + + fun toFinished(id: ResourceLocation): FinishedRecipe { + return SERIALIZER.toFinished(this, id) + } + + override fun getOutput(container: Container): ItemStack { + if (container.isEmpty) return ItemStack.EMPTY + + var output = container[0].copy() + + dyes.forEach { entry -> + if (entry.key == null) { + (output.item as DyeableLeatherItem).clearColor(output) + } else { + output = DyeableLeatherItem.dyeArmor(output.copy(), listOf(DyeItem.byColor(entry.key!!))) + } + } + + return output + } + + companion object { + private val wrapperCodec = StringRepresentable.fromEnum(DyeColorWrapper::values) + + val SERIALIZER = Codec2RecipeSerializer { _ -> + RecordCodecBuilder.create { + it.group( + PredicatedCodecList>( + wrapperCodec.xmap({ mapOf(it to 1) }, { it.keys.first() }) to Predicate { it.keys.size == 1 && it.values.first() == 1 }, + Codec.list(wrapperCodec).xmap({ it.associateWith { 1 } }, { ArrayList(it.keys) }) to Predicate { it.values.all { it == 1 } }, + Codec.unboundedMap(wrapperCodec, Codec.INT.minRange(1)) to Predicate { true } + ).fieldOf("dyes").xmap({ it.mapKeys { it.key.key } }, { it.mapKeys { k -> DyeColorWrapper.entries.first { k.key == it.key } } }).forGetter(PainterArmorDyeRecipe::dyes), + ).apply(it, ::PainterArmorDyeRecipe) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt index 84c436e65..d3cc8c922 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRecipes.kt @@ -34,6 +34,7 @@ object MRecipes { val PLATE_PRESS by register("plate_press") val PAINTER by register("painter") + val PAINTER_ARMOR_DYE by register("painter_armor_dye") val MATTER_ENTANGLER by register("matter_entangler") val MICROWAVE by register("microwave") @@ -43,6 +44,7 @@ object MRecipes { serializers.register("upgrade") { UpgradeRecipe.CODEC } serializers.register("hammer_priming") { ExplosiveHammerPrimingRecipe.CODEC } serializers.register("painter") { PainterRecipe.SERIALIZER } + serializers.register("painter_armor_dye") { PainterArmorDyeRecipe.SERIALIZER } serializers.register("matter_entangler") { MatterEntanglerRecipe.SERIALIZER } serializers.register("matter_entangler_energetic") { MatterEntanglerRecipe.ENERGY_SERIALIZER } serializers.register("matter_entangler_matter") { MatterEntanglerRecipe.MATTER_SERIALIZER }