Painter table water ingredient, semi-realistic color mixing

This commit is contained in:
DBotThePony 2023-10-26 20:51:26 +07:00
parent 474a19a30b
commit 636afe639e
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 211 additions and 66 deletions

View File

@ -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)

View File

@ -50,7 +50,7 @@ private fun cleaning(consumer: RecipeOutput, to: Item, from: Map<out DyeColor?,
consumer.accept(PainterRecipe(
Ingredient.of(from.entries.stream().filter { it.key != null }.map { ItemStack(it.value) }),
ItemStack(to),
setOf()
mapOf(null to 15)
).toFinished(modLocation("painter/cleaning/" + to.recipeName)))
}

View File

@ -1,7 +1,11 @@
package ru.dbotthepony.mc.otm.block.entity.decorative
import com.google.common.collect.ImmutableList
import com.mojang.datafixers.util.Either
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
import it.unimi.dsi.fastutil.objects.Object2IntMap
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.entity.player.Inventory
@ -10,25 +14,31 @@ import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.state.BlockState
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.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.immutableMap
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.nbt.mapPresent
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import java.util.*
class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.PAINTER, blockPos, blockState) {
class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.PAINTER, blockPos, blockState), IFluidHandler {
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
return PainterMenu(containerID, inventory, this)
}
val dyeInput = MatteryContainer(this::markDirtyFast, 1)
private val dyeStored = EnumMap<DyeColor, Int>(DyeColor::class.java)
val dyeStoredView: Map<DyeColor, Int> = Collections.unmodifiableMap(dyeStored)
// null - water
val dyeStored = Object2IntArrayMap<DyeColor?>()
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<DyeColor, Int>) {
fun takeDyes(dyes: Map<out DyeColor?, Int>) {
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<DyeColor>) : Map.Entry<DyeColor, Mixer> {
val mixing: ImmutableList<ImmutableList<DyeColor>> = ImmutableList.copyOf(mixing)
class Mixer(val color: DyeColor, vararg mixing: ImmutableList<Optional<DyeColor>>) : Map.Entry<DyeColor, Mixer> {
val mixing: ImmutableList<ImmutableList<Optional<DyeColor>>> = ImmutableList.copyOf(mixing)
override val key: DyeColor
get() = color
override val value: Mixer
get() = this
private fun mix(input: MutableMap<DyeColor, Int>, seen: MutableSet<DyeColor>, stack: MutableSet<DyeColor>) {
if (input[color]!! > 0 || color in seen || color in stack) return
private fun mix(input: Object2IntMap<DyeColor?>, seen: MutableSet<DyeColor>, stack: MutableSet<DyeColor>) {
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<DyeColor, Int>) {
fun mix(input: Object2IntMap<DyeColor?>) {
mix(input, EnumSet.noneOf(DyeColor::class.java), EnumSet.noneOf(DyeColor::class.java))
}
fun isAvailable(input: Map<DyeColor, Int>): Boolean {
if (input[color]!! > 0) return true
return EnumMap(input).also(::mix)[color]!! > 0
fun isAvailable(input: Object2IntMap<DyeColor?>): 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<DyeColor, Mixer> {
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
}
}

View File

@ -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<PainterMenu>(menu, inventory, title) {
private inner class Bar(parent: EditablePanel<*>, val dye: DyeColor) : EditablePanel<PainterScreen>(this@PainterScreen, parent, width = 5f) {
private inner class Bar(parent: EditablePanel<*>, val dye: DyeColor?) : EditablePanel<PainterScreen>(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())

View File

@ -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<PainterRecipe>, 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++

View File

@ -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<DyeColor?>().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))
}
}

View File

@ -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<DyeColor, Int>
dyes: Map<out DyeColor?, Int>
) : Recipe<Container> {
constructor(
input: Ingredient,
output: ItemStack,
dyes: Set<DyeColor>
) : this(input, output, dyes.associateWith { 1 })
dyes: Set<DyeColor?>
) : this(input, output, Object2IntArrayMap<DyeColor?>().also { map -> dyes.forEach { map[it] = 1 } })
fun canCraft(storedDyes: Map<DyeColor, Int>): Boolean {
val dyes: Object2IntMap<DyeColor?> = Object2IntMaps.unmodifiable(Object2IntArrayMap(dyes))
fun canCraft(storedDyes: Map<out DyeColor?, Int>): 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<PainterRecipe> { context ->
RecordCodecBuilder.create {
it.group(
context.ingredients.fieldOf("input").forGetter(PainterRecipe::input),
ItemStack.CODEC.fieldOf("output").forGetter(PainterRecipe::output),
PredicatedCodecList<Map<DyeColor, Int>>(
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<Map<DyeColorWrapper, Int>>(
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)
}
}