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) .build(consumer)
MatteryRecipe(MItems.PAINTER, category = machinesCategory) 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, Items.BUCKET, MItemTags.IRON_PLATES)
.row(MItemTags.IRON_PLATES, MItemTags.CRAFTING_TABLES, MItemTags.IRON_PLATES) .row(MItemTags.IRON_PLATES, MItemTags.CRAFTING_TABLES, MItemTags.IRON_PLATES)
.unlockedBy(Tags.Items.DYES) .unlockedBy(Tags.Items.DYES)

View File

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

View File

@ -1,7 +1,11 @@
package ru.dbotthepony.mc.otm.block.entity.decorative package ru.dbotthepony.mc.otm.block.entity.decorative
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.mojang.datafixers.util.Either
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap 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.core.BlockPos
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.world.entity.player.Inventory 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.DyeColor
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.state.BlockState 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.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer 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.immutableList
import ru.dbotthepony.mc.otm.core.immutableMap 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.mapPresent
import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
import java.util.* 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 { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
return PainterMenu(containerID, inventory, this) return PainterMenu(containerID, inventory, this)
} }
val dyeInput = MatteryContainer(this::markDirtyFast, 1) val dyeInput = MatteryContainer(this::markDirtyFast, 1)
private val dyeStored = EnumMap<DyeColor, Int>(DyeColor::class.java) // null - water
val dyeStoredView: Map<DyeColor, Int> = Collections.unmodifiableMap(dyeStored) val dyeStored = Object2IntArrayMap<DyeColor?>()
var isBulk = false var isBulk = false
set(value) { set(value) {
@ -36,19 +46,63 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
markDirtyFast() 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 { init {
addDroppableContainer(dyeInput) addDroppableContainer(dyeInput)
savetables.stateful(dyeInput, INVENTORY_KEY) savetables.stateful(dyeInput, INVENTORY_KEY)
savetables.bool(::isBulk) 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 ((dye, amount) in dyes) {
for (i in 0 until amount) { if (dye == null) {
mixer(dye).mix(dyeStored) 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) { if (dyeStored(dye) > 0) {
dyeStored[dye] = dyeStored(dye) - 1 dyeStored[dye] = dyeStored(dye) - 1
}
} }
} }
} }
@ -58,6 +112,16 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
val config = ConfigurableItemHandler(input = dyeInput.handler(object : HandlerFilter { val config = ConfigurableItemHandler(input = dyeInput.handler(object : HandlerFilter {
override fun canInsert(slot: Int, stack: ItemStack): Boolean { 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 val dye = DyeColor.entries.firstOrNull { stack.`is`(it.tag) } ?: return false
return dyeStored(dye) + HUE_PER_ITEM <= MAX_STORAGE return dyeStored(dye) + HUE_PER_ITEM <= MAX_STORAGE
} }
@ -75,14 +139,12 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
} }
})) }))
fun dyeStored(dye: DyeColor): Int { fun waterStored(): Int {
return dyeStored[dye]!! return dyeStored.getInt(null)
} }
init { fun dyeStored(dye: DyeColor?): Int {
for (dye in DyeColor.entries) { return dyeStored.getInt(dye)
dyeStored[dye] = 0
}
} }
override fun saveShared(nbt: CompoundTag) { override fun saveShared(nbt: CompoundTag) {
@ -90,7 +152,7 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
nbt["dyes"] = CompoundTag().also { nbt["dyes"] = CompoundTag().also {
for ((k, v) in dyeStored) { 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) { override fun load(nbt: CompoundTag) {
super.load(nbt) super.load(nbt)
for (dye in DyeColor.entries) { dyeStored.clear()
dyeStored[dye] = 0
}
nbt.mapPresent("dyes") { it: CompoundTag -> nbt.mapPresent("dyes") { it: CompoundTag ->
for (k in it.allKeys) { 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() super.tick()
for (slot in dyeInput.slotIterator()) { 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 dye = DyeColor.entries.firstOrNull { slot.item.`is`(it.tag) } ?: continue
val stored = dyeStored(dye) 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> { class Mixer(val color: DyeColor, vararg mixing: ImmutableList<Optional<DyeColor>>) : Map.Entry<DyeColor, Mixer> {
val mixing: ImmutableList<ImmutableList<DyeColor>> = ImmutableList.copyOf(mixing) val mixing: ImmutableList<ImmutableList<Optional<DyeColor>>> = ImmutableList.copyOf(mixing)
override val key: DyeColor override val key: DyeColor
get() = color get() = color
override val value: Mixer override val value: Mixer
get() = this get() = this
private fun mix(input: MutableMap<DyeColor, Int>, seen: MutableSet<DyeColor>, stack: MutableSet<DyeColor>) { private fun mix(input: Object2IntMap<DyeColor?>, seen: MutableSet<DyeColor>, stack: MutableSet<DyeColor>) {
if (input[color]!! > 0 || color in seen || color in stack) return if (input.getInt(color) > 0 || color in seen || color in stack) return
stack.add(color) stack.add(color)
for (ingredients in mixing) { for (ingredients in mixing) {
val copy = EnumMap(input) val copy = Object2IntArrayMap(input)
var i = 0 var i = 0
val volumes = Int2IntArrayMap() val volumes = Int2IntArrayMap()
@ -146,8 +220,11 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
for (i2 in ingredients.indices) for (i2 in ingredients.indices)
volumes[i2] = 1 volumes[i2] = 1
while (!ingredients.all { copy[it]!! > 0 } && i++ < 20) { while (!ingredients.all { copy.getInt(it.orElse(null)) > 0 } && i++ < 20) {
ingredients.withIndex().forEach { for (t in 0 until volumes[it.index]) mixer(it.value).mix(copy, seen, stack) } ingredients.withIndex().forEach {
for (t in 0 until volumes[it.index])
it.value.ifPresent { mixer(it)?.mix(copy, seen, stack) }
}
var increase = i var increase = i
val iter = ingredients.indices.iterator() val iter = ingredients.indices.iterator()
@ -157,8 +234,8 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
} }
} }
if (ingredients.all { copy[it]!! > 0 }) { if (ingredients.all { copy.getInt(it.orElse(null)) > 0 }) {
ingredients.forEach { copy[it] = copy[it]!! - 1 } ingredients.forEach { copy[it.orElse(null)] = copy.getInt(it.orElse(null)) - 1 }
copy[color] = ingredients.size copy[color] = ingredients.size
input.putAll(copy) input.putAll(copy)
@ -171,29 +248,38 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe
seen.add(color) 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)) mix(input, EnumSet.noneOf(DyeColor::class.java), EnumSet.noneOf(DyeColor::class.java))
} }
fun isAvailable(input: Map<DyeColor, Int>): Boolean { fun isAvailable(input: Object2IntMap<DyeColor?>): Boolean {
if (input[color]!! > 0) return true if (input.getInt(color) > 0) return true
return EnumMap(input).also(::mix)[color]!! > 0 return Object2IntArrayMap(input).also(::mix).getInt(color) > 0
} }
} }
companion object { companion object {
fun mixer(color: DyeColor): Mixer { fun mixer(color: DyeColor): Mixer? {
return MIXING[color]!! return MIXING[color]
} }
val MIXING = immutableMap { val MIXING = immutableMap<DyeColor, Mixer> {
put(Mixer(DyeColor.BLACK, immutableList(DyeColor.CYAN, DyeColor.MAGENTA, DyeColor.YELLOW))) put(Mixer(DyeColor.BLACK, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.YELLOW))))
put(Mixer(DyeColor.RED, immutableList(DyeColor.MAGENTA, DyeColor.YELLOW))) put(Mixer(DyeColor.RED, immutableList(Optional.of(DyeColor.MAGENTA), Optional.of(DyeColor.YELLOW))))
put(Mixer(DyeColor.GREEN, immutableList(DyeColor.CYAN, DyeColor.YELLOW))) put(Mixer(DyeColor.GREEN, immutableList(Optional.of(DyeColor.CYAN), Optional.of(DyeColor.YELLOW))))
put(Mixer(DyeColor.BLUE, immutableList(DyeColor.CYAN, DyeColor.MAGENTA))) 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 MAX_STORAGE = 256
const val HUE_PER_ITEM = 32 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 import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu
class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) : MatteryScreen<PainterMenu>(menu, inventory, title) { 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 { init {
dock = Dock.RIGHT dock = Dock.RIGHT
dockLeft = 1f 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(""))
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) { override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
graphics.renderRect(0f, 0f, width, height, color = RGBAColor.DARK_GRAY) 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)) 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) graphics.renderRect(0f, height * (1f - multiplier), width, height * multiplier, color = color)
tooltips[tooltips.size - 1] = TextComponent("${menu.dyeStoredDirect[dye]} (${(multiplier * 100f).toInt()}%)") 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 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.dock = Dock.RIGHT
it.dockLeft = 4f it.dockLeft = 4f
Bar(it, null).childrenOrder = -1000
EditablePanel(this, it, height = 46f).also { EditablePanel(this, it, height = 46f).also {
it.dock = Dock.TOP it.dock = Dock.TOP
@ -141,10 +148,17 @@ class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) :
recipe.value.dyes.forEach { recipe.value.dyes.forEach {
val (dye, amount) = it val (dye, amount) = it
if (amount == 1) if (dye == null) {
list.add(TranslatableComponent("otm.gui.needs", TranslatableComponent("item.minecraft.${dye.getName()}_dye"))) if (amount == 1)
else if (amount > 1) list.add(TranslatableComponent("otm.gui.needs", TranslatableComponent("block.minecraft.water")))
list.add(TranslatableComponent("otm.gui.needs_x", TranslatableComponent("item.minecraft.${dye.getName()}_dye"), amount)) 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()) 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.network.chat.Component
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.crafting.Ingredient import net.minecraft.world.item.crafting.Ingredient
import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.client.render.ItemStackIcon import ru.dbotthepony.mc.otm.client.render.ItemStackIcon
@ -55,7 +56,7 @@ object PainterRecipeCategory : IRecipeCategory<PainterRecipe>, IDrawable {
for ((dye, count) in recipe.dyes) { for ((dye, count) in recipe.dyes) {
builder.addSlot(RecipeIngredientRole.CATALYST, 1 + x * 18, 1 + y * 18) 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++ 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.DyeColor
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.RecipeHolder 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.block.entity.decorative.PainterBlockEntity
import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.container.MatteryContainer 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.filter
import ru.dbotthepony.mc.otm.core.collect.find import ru.dbotthepony.mc.otm.core.collect.find
import ru.dbotthepony.mc.otm.core.collect.maybe 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.isNotEmpty
import ru.dbotthepony.mc.otm.core.map import ru.dbotthepony.mc.otm.core.map
import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator
@ -34,7 +39,7 @@ import kotlin.collections.ArrayList
class PainterMenu( class PainterMenu(
containerId: Int, inventory: Inventory, tile: PainterBlockEntity? = null containerId: Int, inventory: Inventory, tile: PainterBlockEntity? = null
) : MatteryMenu(MMenus.PAINTER, containerId, inventory, tile) { ) : 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() }) } 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) { val dyeSlot = object : MatterySlot(tile?.dyeInput ?: SimpleContainer(1), 0) {
override fun mayPlace(itemStack: ItemStack): Boolean { 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 package ru.dbotthepony.mc.otm.recipe
import com.google.common.collect.ImmutableMap
import com.mojang.serialization.Codec import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder 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.NonNullList
import net.minecraft.core.RegistryAccess import net.minecraft.core.RegistryAccess
import net.minecraft.data.recipes.FinishedRecipe import net.minecraft.data.recipes.FinishedRecipe
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.util.StringRepresentable
import net.minecraft.world.Container import net.minecraft.world.Container
import net.minecraft.world.item.DyeColor import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.ItemStack 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 ru.dbotthepony.mc.otm.registry.MRecipes
import java.util.EnumMap import java.util.EnumMap
import java.util.function.Predicate import java.util.function.Predicate
import java.util.stream.Collector
import java.util.stream.Collectors
class PainterRecipe( class PainterRecipe(
val input: Ingredient, val input: Ingredient,
val output: ItemStack, val output: ItemStack,
val dyes: Map<DyeColor, Int> dyes: Map<out DyeColor?, Int>
) : Recipe<Container> { ) : Recipe<Container> {
constructor( constructor(
input: Ingredient, input: Ingredient,
output: ItemStack, output: ItemStack,
dyes: Set<DyeColor> dyes: Set<DyeColor?>
) : this(input, output, dyes.associateWith { 1 }) ) : 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 (isIncomplete) return false
if (dyes.isEmpty() || dyes.values.none { it > 0 }) return true if (dyes.isEmpty() || dyes.values.none { it > 0 }) return true
val copy = EnumMap(storedDyes) val copy = Object2IntArrayMap(storedDyes)
for ((dye, amount) in dyes) { for ((dye, amount) in dyes) {
for (i in 0 until amount) { for (i in 0 until amount) {
PainterBlockEntity.mixer(dye).mix(copy) if (dye != null) PainterBlockEntity.mixer(dye)?.mix(copy)
if (copy[dye]!! <= 0) return false if (copy.getInt(dye) <= 0) return false
copy[dye] = copy[dye]!! - 1 copy[dye] = copy.getInt(dye) - 1
} }
} }
@ -109,17 +118,43 @@ class PainterRecipe(
return SERIALIZER.toFinished(this, id) 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 { companion object {
private val wrapperCodec = StringRepresentable.fromEnum(DyeColorWrapper::values)
val SERIALIZER = Codec2RecipeSerializer<PainterRecipe> { context -> val SERIALIZER = Codec2RecipeSerializer<PainterRecipe> { context ->
RecordCodecBuilder.create { RecordCodecBuilder.create {
it.group( it.group(
context.ingredients.fieldOf("input").forGetter(PainterRecipe::input), context.ingredients.fieldOf("input").forGetter(PainterRecipe::input),
ItemStack.CODEC.fieldOf("output").forGetter(PainterRecipe::output), ItemStack.CODEC.fieldOf("output").forGetter(PainterRecipe::output),
PredicatedCodecList<Map<DyeColor, Int>>( PredicatedCodecList<Map<DyeColorWrapper, Int>>(
DyeColor.CODEC.xmap({ mapOf(it to 1) }, { it.keys.first() }) to Predicate { it.keys.size == 1 && it.values.first() == 1 }, wrapperCodec.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.list(wrapperCodec).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 } Codec.unboundedMap(wrapperCodec, Codec.INT.minRange(1)) to Predicate { true }
).fieldOf("dyes").forGetter(PainterRecipe::dyes), ).fieldOf("dyes").xmap({ it.mapKeys { it.key.key } }, { it.mapKeys { k -> DyeColorWrapper.entries.first { k.key == it.key } } }).forGetter(PainterRecipe::dyes),
).apply(it, ::PainterRecipe) ).apply(it, ::PainterRecipe)
} }
} }