From 636afe639ebba98a406ee2a70cef6279848fd36f Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Thu, 26 Oct 2023 20:51:26 +0700 Subject: [PATCH] Painter table water ingredient, semi-realistic color mixing --- .../datagen/recipes/CraftingTableRecipes.kt | 2 +- .../mc/otm/datagen/recipes/PainterRecipes.kt | 2 +- .../entity/decorative/PainterBlockEntity.kt | 164 +++++++++++++----- .../client/screen/decorative/PainterScreen.kt | 32 +++- .../otm/compat/jei/PainterRecipeCategory.kt | 3 +- .../mc/otm/menu/decorative/PainterMenu.kt | 13 +- .../mc/otm/recipe/PainterRecipe.kt | 61 +++++-- 7 files changed, 211 insertions(+), 66 deletions(-) diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt index 4d8bd96b8..509e9e9f6 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/recipes/CraftingTableRecipes.kt @@ -445,7 +445,7 @@ fun addCraftingTableRecipes(consumer: RecipeOutput) { .build(consumer) MatteryRecipe(MItems.PAINTER, category = machinesCategory) - .row(Items.BRUSH, Items.BUCKET, Items.WATER_BUCKET) + .row(Items.BRUSH, Items.BUCKET, Items.BUCKET) .row(MItemTags.IRON_PLATES, Items.BUCKET, MItemTags.IRON_PLATES) .row(MItemTags.IRON_PLATES, MItemTags.CRAFTING_TABLES, MItemTags.IRON_PLATES) .unlockedBy(Tags.Items.DYES) 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 7891470df..3a283b803 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 @@ -50,7 +50,7 @@ private fun cleaning(consumer: RecipeOutput, to: Item, from: Map(DyeColor::class.java) - val dyeStoredView: Map = Collections.unmodifiableMap(dyeStored) + // null - water + val dyeStored = Object2IntArrayMap() var isBulk = false set(value) { @@ -36,19 +46,63 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe markDirtyFast() } + override fun getTanks(): Int { + return 1 + } + + override fun getFluidInTank(tank: Int): FluidStack { + if (tank != 1) return FluidStack.EMPTY + if (waterStored() == 0) return FluidStack.EMPTY + return FluidStack(Fluids.WATER, waterStored()) + } + + override fun getTankCapacity(tank: Int): Int { + if (tank == 1) return MAX_WATER_STORAGE + return 0 + } + + override fun isFluidValid(tank: Int, stack: FluidStack): Boolean { + return tank == 0 && stack.fluid == Fluids.WATER + } + + override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int { + if (resource.isEmpty || resource.fluid != Fluids.WATER || waterStored() >= MAX_WATER_STORAGE) return 0 + val diff = (waterStored() + resource.amount).coerceAtMost(MAX_WATER_STORAGE) - waterStored() + if (action.simulate() || diff == 0) return diff + dyeStored[null] = waterStored() + diff + return diff + } + + override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack { + return FluidStack.EMPTY + } + + override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { + return FluidStack.EMPTY + } + init { addDroppableContainer(dyeInput) savetables.stateful(dyeInput, INVENTORY_KEY) savetables.bool(::isBulk) + exposeGlobally(ForgeCapabilities.FLUID_HANDLER, this) } - fun takeDyes(dyes: Map) { + fun takeDyes(dyes: Map) { for ((dye, amount) in dyes) { - for (i in 0 until amount) { - mixer(dye).mix(dyeStored) + if (dye == null) { + for (i in 0 until amount) { + if (waterStored() >= 1) { + dyeStored[dye] = waterStored() - 1 + } + } + } else { + for (i in 0 until amount) { + mixer(dye)?.mix(dyeStored) - if (dyeStored(dye) > 0) { - dyeStored[dye] = dyeStored(dye) - 1 + if (dyeStored(dye) > 0) { + dyeStored[dye] = dyeStored(dye) - 1 + } } } } @@ -58,6 +112,16 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe val config = ConfigurableItemHandler(input = dyeInput.handler(object : HandlerFilter { override fun canInsert(slot: Int, stack: ItemStack): Boolean { + if (waterStored() < MAX_WATER_STORAGE) { + stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + val drain = it.drain(FluidStack(Fluids.WATER, MAX_WATER_STORAGE - waterStored()), IFluidHandler.FluidAction.SIMULATE) + + if (drain.isNotEmpty) { + return true + } + } + } + val dye = DyeColor.entries.firstOrNull { stack.`is`(it.tag) } ?: return false return dyeStored(dye) + HUE_PER_ITEM <= MAX_STORAGE } @@ -75,14 +139,12 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe } })) - fun dyeStored(dye: DyeColor): Int { - return dyeStored[dye]!! + fun waterStored(): Int { + return dyeStored.getInt(null) } - init { - for (dye in DyeColor.entries) { - dyeStored[dye] = 0 - } + fun dyeStored(dye: DyeColor?): Int { + return dyeStored.getInt(dye) } override fun saveShared(nbt: CompoundTag) { @@ -90,7 +152,7 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe nbt["dyes"] = CompoundTag().also { for ((k, v) in dyeStored) { - it[k.getName()] = v + it[k?.getName() ?: "water"] = v } } } @@ -98,13 +160,14 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe override fun load(nbt: CompoundTag) { super.load(nbt) - for (dye in DyeColor.entries) { - dyeStored[dye] = 0 - } + dyeStored.clear() nbt.mapPresent("dyes") { it: CompoundTag -> for (k in it.allKeys) { - dyeStored[DyeColor.entries.firstOrNull { it.getName() == k } ?: continue] = it.getInt(k) + if (k == "water") + dyeStored[null] = it.getInt("water") + else + dyeStored[DyeColor.entries.firstOrNull { it.getName() == k } ?: continue] = it.getInt(k) } } } @@ -113,6 +176,17 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe super.tick() for (slot in dyeInput.slotIterator()) { + if (waterStored() < MAX_WATER_STORAGE) { + slot.item.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresentK { + val drain = it.drain(FluidStack(Fluids.WATER, MAX_WATER_STORAGE - waterStored()), IFluidHandler.FluidAction.EXECUTE) + + if (drain.isNotEmpty) { + dyeStored[null] = waterStored() + drain.amount + slot.item = it.container + } + } + } + val dye = DyeColor.entries.firstOrNull { slot.item.`is`(it.tag) } ?: continue val stored = dyeStored(dye) @@ -124,20 +198,20 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe } } - class Mixer(val color: DyeColor, vararg mixing: ImmutableList) : Map.Entry { - val mixing: ImmutableList> = ImmutableList.copyOf(mixing) + class Mixer(val color: DyeColor, vararg mixing: ImmutableList>) : Map.Entry { + val mixing: ImmutableList>> = ImmutableList.copyOf(mixing) override val key: DyeColor get() = color override val value: Mixer get() = this - private fun mix(input: MutableMap, seen: MutableSet, stack: MutableSet) { - if (input[color]!! > 0 || color in seen || color in stack) return + private fun mix(input: Object2IntMap, seen: MutableSet, stack: MutableSet) { + if (input.getInt(color) > 0 || color in seen || color in stack) return stack.add(color) for (ingredients in mixing) { - val copy = EnumMap(input) + val copy = Object2IntArrayMap(input) var i = 0 val volumes = Int2IntArrayMap() @@ -146,8 +220,11 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe for (i2 in ingredients.indices) volumes[i2] = 1 - while (!ingredients.all { copy[it]!! > 0 } && i++ < 20) { - ingredients.withIndex().forEach { for (t in 0 until volumes[it.index]) mixer(it.value).mix(copy, seen, stack) } + while (!ingredients.all { copy.getInt(it.orElse(null)) > 0 } && i++ < 20) { + ingredients.withIndex().forEach { + for (t in 0 until volumes[it.index]) + it.value.ifPresent { mixer(it)?.mix(copy, seen, stack) } + } var increase = i val iter = ingredients.indices.iterator() @@ -157,8 +234,8 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe } } - if (ingredients.all { copy[it]!! > 0 }) { - ingredients.forEach { copy[it] = copy[it]!! - 1 } + if (ingredients.all { copy.getInt(it.orElse(null)) > 0 }) { + ingredients.forEach { copy[it.orElse(null)] = copy.getInt(it.orElse(null)) - 1 } copy[color] = ingredients.size input.putAll(copy) @@ -171,29 +248,38 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe seen.add(color) } - fun mix(input: MutableMap) { + fun mix(input: Object2IntMap) { mix(input, EnumSet.noneOf(DyeColor::class.java), EnumSet.noneOf(DyeColor::class.java)) } - fun isAvailable(input: Map): Boolean { - if (input[color]!! > 0) return true - return EnumMap(input).also(::mix)[color]!! > 0 + fun isAvailable(input: Object2IntMap): Boolean { + if (input.getInt(color) > 0) return true + return Object2IntArrayMap(input).also(::mix).getInt(color) > 0 } } companion object { - fun mixer(color: DyeColor): Mixer { - return MIXING[color]!! + fun mixer(color: DyeColor): Mixer? { + return MIXING[color] } - val MIXING = immutableMap { - put(Mixer(DyeColor.BLACK, immutableList(DyeColor.CYAN, DyeColor.MAGENTA, DyeColor.YELLOW))) - put(Mixer(DyeColor.RED, immutableList(DyeColor.MAGENTA, DyeColor.YELLOW))) - put(Mixer(DyeColor.GREEN, immutableList(DyeColor.CYAN, DyeColor.YELLOW))) - put(Mixer(DyeColor.BLUE, immutableList(DyeColor.CYAN, DyeColor.MAGENTA))) + val MIXING = immutableMap { + put(Mixer(DyeColor.BLACK, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.YELLOW)))) + put(Mixer(DyeColor.RED, immutableList(Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.YELLOW)))) + put(Mixer(DyeColor.GREEN, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.YELLOW)))) + put(Mixer(DyeColor.BLUE, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.MAGENTA)))) + put(Mixer(DyeColor.LIGHT_BLUE, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.BLUE)))) + put(Mixer(DyeColor.LIME, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.GREEN)))) + put(Mixer(DyeColor.ORANGE, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.RED), Optional.empty()))) + put(Mixer(DyeColor.PURPLE, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.MAGENTA), Optional.empty()))) + put(Mixer(DyeColor.BROWN, immutableList(Optional.of(DyeColor.YELLOW), Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.BLACK), Optional.empty()))) + put(Mixer(DyeColor.PINK, immutableList(Optional.of(DyeColor.YELLOW), Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.WHITE), Optional.empty()))) + put(Mixer(DyeColor.GRAY, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.BLACK), Optional.empty()))) + put(Mixer(DyeColor.LIGHT_GRAY, immutableList(Optional.of(DyeColor.WHITE), Optional.of(DyeColor.WHITE), Optional.of(DyeColor.BLACK), Optional.empty()))) } const val MAX_STORAGE = 256 const val HUE_PER_ITEM = 32 + const val MAX_WATER_STORAGE = 4_000 } } 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 792b6cfde..30bd9e6b2 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 @@ -34,11 +34,16 @@ import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu 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) { + private inner class Bar(parent: EditablePanel<*>, val dye: DyeColor?) : EditablePanel(this@PainterScreen, parent, width = 5f) { init { dock = Dock.RIGHT dockLeft = 1f - tooltips.add(TranslatableComponent("item.minecraft.${dye.getName()}_dye")) + + if (dye == null) + tooltips.add(TranslatableComponent("block.minecraft.water")) + else + tooltips.add(TranslatableComponent("item.minecraft.${dye.getName() ?: "water"}_dye")) + tooltips.add(TextComponent("")) tooltips.add(TextComponent("")) } @@ -46,10 +51,10 @@ class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) : override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { graphics.renderRect(0f, 0f, width, height, color = RGBAColor.DARK_GRAY) - val color = RGBAColor.rgb(dye.textColor) + val color = RGBAColor.rgb(dye?.textColor ?: DyeColor.LIGHT_BLUE.textColor) graphics.renderRect(0f, 0f, width, height, color = color.copy(alpha = 0.4f)) - val multiplier = menu.dyeStoredDirect[dye]!!.toFloat() / PainterBlockEntity.MAX_STORAGE.toFloat() + val multiplier = menu.dyeStoredDirect[dye]!!.toFloat() / (if (dye == null) PainterBlockEntity.MAX_WATER_STORAGE.toFloat() else PainterBlockEntity.MAX_STORAGE.toFloat()) graphics.renderRect(0f, height * (1f - multiplier), width, height * multiplier, color = color) tooltips[tooltips.size - 1] = TextComponent("${menu.dyeStoredDirect[dye]} (${(multiplier * 100f).toInt()}%)") @@ -68,10 +73,12 @@ class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) : it.dockTop = 4f } - EditablePanel(this, frame, width = 6f * DyeColor.entries.size / 2f).also { + EditablePanel(this, frame, width = 6f * (DyeColor.entries.size / 2f + 1f)).also { it.dock = Dock.RIGHT it.dockLeft = 4f + Bar(it, null).childrenOrder = -1000 + EditablePanel(this, it, height = 46f).also { it.dock = Dock.TOP @@ -141,10 +148,17 @@ class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) : recipe.value.dyes.forEach { val (dye, amount) = it - if (amount == 1) - list.add(TranslatableComponent("otm.gui.needs", TranslatableComponent("item.minecraft.${dye.getName()}_dye"))) - else if (amount > 1) - list.add(TranslatableComponent("otm.gui.needs_x", TranslatableComponent("item.minecraft.${dye.getName()}_dye"), amount)) + if (dye == null) { + if (amount == 1) + list.add(TranslatableComponent("otm.gui.needs", TranslatableComponent("block.minecraft.water"))) + else if (amount > 1) + list.add(TranslatableComponent("otm.gui.needs_x", TranslatableComponent("block.minecraft.water"), amount)) + } else { + if (amount == 1) + list.add(TranslatableComponent("otm.gui.needs", TranslatableComponent("item.minecraft.${dye.getName() ?: "water"}_dye"))) + else if (amount > 1) + list.add(TranslatableComponent("otm.gui.needs_x", TranslatableComponent("item.minecraft.${dye.getName() ?: "water"}_dye"), amount)) + } } graphics.renderComponentTooltip(font, list, mouseX.toInt(), mouseY.toInt()) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt index a948eae2f..d6dfafbe4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jei/PainterRecipeCategory.kt @@ -11,6 +11,7 @@ import net.minecraft.client.gui.GuiGraphics import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items import net.minecraft.world.item.crafting.Ingredient import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.client.render.ItemStackIcon @@ -55,7 +56,7 @@ object PainterRecipeCategory : IRecipeCategory, IDrawable { for ((dye, count) in recipe.dyes) { builder.addSlot(RecipeIngredientRole.CATALYST, 1 + x * 18, 1 + y * 18) - .addIngredients(VanillaTypes.ITEM_STACK, Ingredient.of(dye.tag).items.map { it.copyWithCount(count) }) + .addIngredients(VanillaTypes.ITEM_STACK, dye?.let { Ingredient.of(it.tag).items.map { it.copyWithCount(count) } } ?: listOf(ItemStack(Items.WATER_BUCKET, count))) y++ 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 2a7966860..94298f266 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 @@ -6,6 +6,10 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.item.DyeColor import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.RecipeHolder +import net.minecraft.world.level.material.Fluids +import net.minecraftforge.common.capabilities.ForgeCapabilities +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.capability.IFluidHandler import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.container.MatteryContainer @@ -15,6 +19,7 @@ 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 @@ -34,7 +39,7 @@ import kotlin.collections.ArrayList class PainterMenu( containerId: Int, inventory: Inventory, tile: PainterBlockEntity? = null ) : MatteryMenu(MMenus.PAINTER, containerId, inventory, tile) { - val dyeStored = DyeColor.entries.associateWith { dye -> + val dyeStored = (DyeColor.entries.toMutableList().also { it.add(0, null) }).associateWith { dye -> mSynchronizer.ComputedIntField({ tile?.dyeStored(dye) ?: 0 }).also { it.addListener(IntConsumer { rescan() }) } } @@ -95,7 +100,11 @@ class PainterMenu( val dyeSlot = object : MatterySlot(tile?.dyeInput ?: SimpleContainer(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { - return super.mayPlace(itemStack) && (DyeColor.getColor(itemStack)?.let { dyeStoredDirect[it]!! + PainterBlockEntity.HUE_PER_ITEM <= PainterBlockEntity.MAX_STORAGE } ?: false) + return super.mayPlace(itemStack) && (( + itemStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).resolve().map { + dyeStoredDirect[null]!! < PainterBlockEntity.MAX_WATER_STORAGE && it.drain(FluidStack(Fluids.WATER, PainterBlockEntity.MAX_WATER_STORAGE - dyeStoredDirect[null]!!), IFluidHandler.FluidAction.SIMULATE).isNotEmpty + }.orElse(false) + ) || (DyeColor.getColor(itemStack)?.let { dyeStoredDirect[it]!! + PainterBlockEntity.HUE_PER_ITEM <= PainterBlockEntity.MAX_STORAGE } ?: false)) } } 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 1f3210b9e..e72cc284e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/PainterRecipe.kt @@ -1,11 +1,16 @@ 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 +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.Object2IntMaps import net.minecraft.core.NonNullList import net.minecraft.core.RegistryAccess 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 @@ -26,28 +31,32 @@ 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, - val dyes: Map + dyes: Map ) : Recipe { constructor( input: Ingredient, output: ItemStack, - dyes: Set - ) : this(input, output, dyes.associateWith { 1 }) + dyes: Set + ) : this(input, output, Object2IntArrayMap().also { map -> dyes.forEach { map[it] = 1 } }) - fun canCraft(storedDyes: Map): Boolean { + val dyes: Object2IntMap = Object2IntMaps.unmodifiable(Object2IntArrayMap(dyes)) + + fun canCraft(storedDyes: Map): Boolean { if (isIncomplete) return false if (dyes.isEmpty() || dyes.values.none { it > 0 }) return true - val copy = EnumMap(storedDyes) + val copy = Object2IntArrayMap(storedDyes) for ((dye, amount) in dyes) { for (i in 0 until amount) { - PainterBlockEntity.mixer(dye).mix(copy) - if (copy[dye]!! <= 0) return false - copy[dye] = copy[dye]!! - 1 + if (dye != null) PainterBlockEntity.mixer(dye)?.mix(copy) + if (copy.getInt(dye) <= 0) return false + copy[dye] = copy.getInt(dye) - 1 } } @@ -109,17 +118,43 @@ class PainterRecipe( 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 + } + } + companion object { + private val wrapperCodec = StringRepresentable.fromEnum(DyeColorWrapper::values) + val SERIALIZER = Codec2RecipeSerializer { context -> RecordCodecBuilder.create { it.group( context.ingredients.fieldOf("input").forGetter(PainterRecipe::input), ItemStack.CODEC.fieldOf("output").forGetter(PainterRecipe::output), - PredicatedCodecList>( - DyeColor.CODEC.xmap({ mapOf(it to 1) }, { it.keys.first() }) to Predicate { it.keys.size == 1 && it.values.first() == 1 }, - Codec.list(DyeColor.CODEC).xmap({ it.associateWith { 1 } }, { ArrayList(it.keys) }) to Predicate { it.values.all { it == 1 } }, - Codec.unboundedMap(DyeColor.CODEC, Codec.INT.minRange(1)) to Predicate { true } - ).fieldOf("dyes").forGetter(PainterRecipe::dyes), + 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(PainterRecipe::dyes), ).apply(it, ::PainterRecipe) } }