Matter entangler, ingredient matrix, shadow containers, quantum battery recipes, more JEI compat

This commit is contained in:
DBotThePony 2023-08-17 22:26:43 +07:00
parent 04524db1a5
commit e6758f1e27
Signed by: DBot
GPG Key ID: DCC23B5715498507
61 changed files with 1480 additions and 152 deletions

View File

@ -48,6 +48,7 @@ import ru.dbotthepony.mc.otm.datagen.models.addBlockModels
import ru.dbotthepony.mc.otm.datagen.recipes.addBlastingRecipes
import ru.dbotthepony.mc.otm.datagen.recipes.addCraftingTableRecipes
import ru.dbotthepony.mc.otm.datagen.recipes.addDecorativesRecipes
import ru.dbotthepony.mc.otm.datagen.recipes.addMatterEntanglerRecipes
import ru.dbotthepony.mc.otm.datagen.recipes.addPlatePressRecipes
import ru.dbotthepony.mc.otm.datagen.recipes.addShapelessRecipes
import ru.dbotthepony.mc.otm.datagen.recipes.addOreSmeltingRecipes
@ -564,6 +565,7 @@ object DataGen {
addShapelessRecipes(consumer)
addOreSmeltingRecipes(consumer)
addPainterRecipes(consumer)
addMatterEntanglerRecipes(consumer)
}
addPlatePressRecipes(recipeProvider)

View File

@ -432,6 +432,7 @@ private fun blocks(provider: MatteryLanguageProvider) {
add(MBlocks.DEV_CHEST, "Dev Chest")
add(MBlocks.DEV_CHEST, "desc", "Contains all items present in game")
add(MBlocks.PAINTER, "Painting Table")
add(MBlocks.MATTER_ENTANGLER, "Matter Entangler")
add(MBlocks.FLUID_TANK, "Fluid Tank")
add(MBlocks.FLUID_TANK, "named", "Fluid Tank (%s)")
@ -713,6 +714,8 @@ private fun gui(provider: MatteryLanguageProvider) {
with(provider.english) {
gui("quicksearch", "Quick search...")
gui("energy_required", "Energy required: %s")
gui("insert_priority", "Insert priority")
gui("extract_priority", "Extract priority")
gui("increase", "Increase")

View File

@ -434,6 +434,7 @@ private fun blocks(provider: MatteryLanguageProvider) {
add(MBlocks.DEV_CHEST, "Сундук разработчика")
add(MBlocks.DEV_CHEST, "desc", "Хранит все предметы, которые есть в игре")
add(MBlocks.PAINTER, "Стол маляра")
add(MBlocks.MATTER_ENTANGLER, "Квантовый запутыватель материи")
add(MBlocks.FLUID_TANK, "Жидкостный бак")
add(MBlocks.FLUID_TANK, "named", "Жидкостный бак (%s)")

View File

@ -134,6 +134,7 @@ fun addLootTables(lootTables: LootTables) {
lootTables.tile(MBlocks.MATTER_RECONSTRUCTOR)
lootTables.tile(MBlocks.FLUID_TANK)
lootTables.tile(MBlocks.PAINTER)
lootTables.tile(MBlocks.MATTER_ENTANGLER)
lootTables.tile(MBlocks.ENERGY_SERVO)
lootTables.tile(MBlocks.ENERGY_COUNTER)

View File

@ -0,0 +1,43 @@
package ru.dbotthepony.mc.otm.datagen.recipes
import net.minecraft.data.recipes.FinishedRecipe
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.Ingredient
import net.minecraftforge.common.Tags
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.datagen.modLocation
import ru.dbotthepony.mc.otm.recipe.IngredientMatrix
import ru.dbotthepony.mc.otm.recipe.MatterEntanglerRecipe
import ru.dbotthepony.mc.otm.registry.MItemTags
import ru.dbotthepony.mc.otm.registry.MItems
import java.util.function.Consumer
fun addMatterEntanglerRecipes(consumer: Consumer<FinishedRecipe>) {
consumer.accept(
MatterEntanglerRecipe(
modLocation("quantum_capacitor"),
IngredientMatrix.of(
listOf(Ingredient.of(MItems.ELECTRIC_PARTS), Ingredient.of(MItemTags.GOLD_WIRES), Ingredient.of(MItems.ELECTRIC_PARTS)),
listOf(Ingredient.of(MItems.BATTERY_CAPACITOR), Ingredient.of(MItems.QUANTUM_TRANSCEIVER), Ingredient.of(MItems.BATTERY_CAPACITOR)),
listOf(Ingredient.of(MItemTags.TRITANIUM_PLATES), Ingredient.of(MItemTags.TRITANIUM_PLATES), Ingredient.of(MItemTags.TRITANIUM_PLATES)),
),
Decimal(40),
400.0,
ItemStack(MItems.QUANTUM_CAPACITOR, 2)
).energetic().toFinished()
)
consumer.accept(
MatterEntanglerRecipe(
modLocation("quantum_battery"),
IngredientMatrix.of(
listOf(Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE), Ingredient.of(MItemTags.GOLD_WIRES), Ingredient.of(Tags.Items.STORAGE_BLOCKS_REDSTONE)),
listOf(Ingredient.of(MItems.BATTERY_DENSE), Ingredient.of(MItems.QUANTUM_TRANSCEIVER), Ingredient.of(MItems.BATTERY_DENSE)),
listOf(Ingredient.of(MItemTags.TRITANIUM_PLATES), Ingredient.of(MItemTags.TRITANIUM_PLATES), Ingredient.of(MItemTags.TRITANIUM_PLATES)),
),
Decimal(120),
600.0,
ItemStack(MItems.QUANTUM_BATTERY, 2)
).energetic().toFinished()
)
}

View File

@ -162,6 +162,7 @@ fun addTags(tagsProvider: TagsProvider) {
MBlocks.PLATE_PRESS,
MBlocks.TWIN_PLATE_PRESS,
MBlocks.MATTER_RECYCLER,
MBlocks.MATTER_ENTANGLER,
MBlocks.POWERED_FURNACE,
MBlocks.POWERED_SMOKER,

View File

@ -306,8 +306,10 @@ abstract class MachineJobEventLoop<JobType : IJob>(val codec: Codec<JobType>) :
}
}
private var lastTickWasError = false
val isUnableToProcess: Boolean
get() = throttleTicks > 0
get() = throttleTicks > 0 || lastTickWasError
val workProgress: Float
get() {
@ -392,6 +394,8 @@ abstract class MachineJobEventLoop<JobType : IJob>(val codec: Codec<JobType>) :
isIdling = false
}
lastTickWasError = false
if (isIdling) {
workingTicksAnim = 0
errorTicksAnim = 0
@ -430,6 +434,7 @@ abstract class MachineJobEventLoop<JobType : IJob>(val codec: Codec<JobType>) :
idleReason = IdleReason.POWER
isIdling = true
idleTicksAnim++
lastTickWasError = true
break
}
@ -467,6 +472,7 @@ abstract class MachineJobEventLoop<JobType : IJob>(val codec: Codec<JobType>) :
if (!status.success) {
throttleTicks += status.throttleTicks
lastTickWasError = true
if (status.idleReason != null) {
idleReason = status.idleReason
@ -505,6 +511,7 @@ abstract class MachineJobEventLoop<JobType : IJob>(val codec: Codec<JobType>) :
errorTicksAnim = 0
} else {
throttleTicks += status.throttleTicks
lastTickWasError = true
if (status.idleReason != null) {
idleReason = status.idleReason

View File

@ -17,7 +17,7 @@ import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.immutableMap
import ru.dbotthepony.mc.otm.core.nbt.mapPresent
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.menu.tech.PainterMenu
import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import java.util.*

View File

@ -0,0 +1,157 @@
package ru.dbotthepony.mc.otm.block.entity.matter
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.entity.ItemJob
import ru.dbotthepony.mc.otm.block.entity.JobContainer
import ru.dbotthepony.mc.otm.block.entity.JobStatus
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.UpgradeType
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl
import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.ShadowCraftingContainer
import ru.dbotthepony.mc.otm.container.UpgradeContainer
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.data.DecimalCodec
import ru.dbotthepony.mc.otm.data.minRange
import ru.dbotthepony.mc.otm.graph.matter.MatterNode
import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MRecipes
class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryWorkerBlockEntity<MatterEntanglerBlockEntity.Job>(MBlockEntities.MATTER_ENTANGLER, blockPos, blockState, Job.CODEC) {
class Job(itemStack: ItemStack, val matter: Decimal, ticks: Double) : ItemJob(itemStack, ticks, MachinesConfig.MATTER_ENTANGLER.energyConsumption) {
val matterPerTick = matter / ticks
companion object {
val CODEC: Codec<Job> = RecordCodecBuilder.create {
it.group(
ItemStack.CODEC.fieldOf("itemStack").forGetter(ItemJob::itemStack),
DecimalCodec.minRange(Decimal.ZERO).fieldOf("matter").forGetter(Job::matter),
Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(ItemJob::ticks),
).apply(it, ::Job)
}
}
}
override val upgrades = UpgradeContainer(::markDirtyFast, 3, UpgradeType.BASIC_MATTER)
override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MATTER_ENTANGLER)))
val matter = ProfiledMatterStorage(MatterStorageImpl(::markDirtyFast, FlowDirection.INPUT, upgrades.matterCapacity(MachinesConfig.MATTER_ENTANGLER::matterCapacity)))
val node = MatterNode()
val energyConfig = ConfigurableEnergy(energy)
val inputs = object : MatteryCraftingContainer(::itemContainerUpdated, 3, 3) {
override fun getMaxStackSize(): Int {
return 1
}
}
val output = object : MatteryContainer(::itemContainerUpdated, 1) {
override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int {
return Int.MAX_VALUE
}
}
val itemConfig = ConfigurableItemHandler(
input = inputs.handler(object : HandlerFilter {
override fun canInsert(slot: Int, stack: ItemStack): Boolean {
val shadow = ShadowCraftingContainer.shadow(inputs, slot, stack)
return (level ?: return false)
.recipeManager
.byType(MRecipes.MATTER_ENTANGLER)
.values
.any { it.preemptivelyMatches(shadow, level!!) }
}
override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
return false
}
}),
output = output.handler(HandlerFilter.OnlyOut)
)
init {
savetables.stateful(::energy, ENERGY_KEY)
savetables.stateful(::matter, MATTER_STORAGE_KEY)
savetables.stateful(::upgrades)
savetables.stateful(::inputs)
savetables.stateful(::output)
exposeGlobally(MatteryCapability.MATTER_NODE, node)
exposeGlobally(MatteryCapability.MATTER, matter)
}
override fun setLevel(level: Level) {
super.setLevel(level)
node.discover(this)
}
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? {
return MatterEntanglerMenu(containerID, inventory, this)
}
override fun onJobTick(status: JobStatus<Job>, id: Int) {
val required = status.job.matterPerTick * status.ticksAdvanced
if (matter.storedMatter < required) {
matter.receiveMatter(node.graph.extractMatter(status.job.matterPerTick.coerceAtLeast(Decimal.TEN).coerceAtMost(matter.missingMatter), false), false)
}
status.scale(matter.extractMatter(required, false) / required)
}
override fun tick() {
super.tick()
if (jobEventLoops[0].currentJob == null) {
matter.extractMatter(node.graph.receiveMatter(matter.storedMatter, false), false)
}
}
override fun onJobFinish(status: JobStatus<Job>, id: Int) {
if (!output.fullyAddItem(status.job.itemStack)) {
status.noItem()
}
}
override fun computeNextJob(id: Int): JobContainer<Job> {
if (!energy.batteryLevel.isPositive)
return JobContainer.noEnergy()
val recipe = (level ?: return JobContainer.failure())
.recipeManager
.byType(MRecipes.MATTER_ENTANGLER)
.values
.firstOrNull { it.matches(inputs, level!!) } ?: return JobContainer.noItem()
val result = recipe.assemble(inputs, level!!.registryAccess())
inputs.forEach { it.shrink(1) }
inputs.setChanged()
return JobContainer.success(
Job(
result,
recipe.matter,
recipe.ticks * MachinesConfig.MATTER_ENTANGLER.workTimeMultiplier
)
)
}
}

View File

@ -0,0 +1,22 @@
package ru.dbotthepony.mc.otm.block.matter
import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.block.entity.matter.MatterEntanglerBlockEntity
class MatterEntanglerBlock : RotatableMatteryBlock(), EntityBlock {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity {
return MatterEntanglerBlockEntity(blockPos, blockState)
}
override fun <T : BlockEntity?> getTicker(p_153212_: Level, p_153213_: BlockState, p_153214_: BlockEntityType<T>): BlockEntityTicker<T>? {
if (p_153212_.isClientSide) return null
return BlockEntityTicker { _, _, _, tile -> if (tile is MatterEntanglerBlockEntity) tile.tick() }
}
}

View File

@ -230,6 +230,8 @@ fun Player.items(includeCosmetics: Boolean = true): Iterator<ItemStack> {
matteryPlayer?.let {
if (it.hasExopack) {
iterators.add(it.exopackContainer.iterator())
iterators.add(it.exopackEnergy.parent.iterator())
iterators.add(it.exopackChargeSlots.iterator())
}
}

View File

@ -1,6 +1,5 @@
package ru.dbotthepony.mc.otm.client.screen
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.gui.screens.inventory.InventoryScreen
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
@ -276,7 +275,7 @@ class ExopackInventoryScreen(menu: ExopackInventoryMenu) : MatteryScreen<Exopack
curios.x = x
}
EffectListPanel(this, frame, menu.ply, x - 56f, gridHeight = 6).tick()
EffectListPanel(this, frame, menu.player, x - 56f, gridHeight = 6).tick()
return frame
}

View File

@ -6,7 +6,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.Font
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
@ -35,6 +34,7 @@ import ru.dbotthepony.mc.otm.client.screen.widget.HorizontalPowerGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.PatternGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledMatterGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledPowerGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.TallHorizontalProfiledPowerGaugePanel
@ -209,7 +209,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
if (menu.playerExoSuitSlots.isEmpty()) {
inventoryFrame = FramePanel<MatteryScreen<*>>(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, INVENTORY_FRAME_HEIGHT, inventory.displayName).also(this::addPanel)
inventoryFrame!!.makeHelpButton().addSlotFiltersHelp().also {
if (menu.ply.matteryPlayer?.hasExopack == true)
if (menu.player.matteryPlayer?.hasExopack == true)
it.tooltips.add(TranslatableComponent("otm.gui.help.slot_charging"))
}
@ -236,7 +236,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
} else {
inventoryFrame = FramePanel<MatteryScreen<*>>(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH_EXTENDED, BASE_INVENTORY_FRAME_HEIGHT + AbstractSlotPanel.SIZE * inventoryRows, inventory.displayName).also(this::addPanel)
inventoryFrame!!.makeHelpButton().addSlotFiltersHelp().also {
if (menu.ply.matteryPlayer?.hasExopack == true)
if (menu.player.matteryPlayer?.hasExopack == true)
it.tooltips.add(TranslatableComponent("otm.gui.help.slot_charging"))
}
@ -421,7 +421,9 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
}
}
if (matter != null) {
if (profiledMatter != null) {
ProfiledMatterGaugePanel(this, gauges, profiledMatter).dock = Dock.LEFT
} else if (matter != null) {
MatterGaugePanel(this, gauges, matter).dock = Dock.LEFT
}

View File

@ -1,4 +1,4 @@
package ru.dbotthepony.mc.otm.client.screen.tech
package ru.dbotthepony.mc.otm.client.screen.decorative
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
@ -27,7 +27,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollableCanvasPanel
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import ru.dbotthepony.mc.otm.menu.tech.PainterMenu
import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu
class PainterScreen(menu: PainterMenu, inventory: Inventory, title: Component) : MatteryScreen<PainterMenu>(menu, inventory, title) {
inner class Bar(parent: EditablePanel<*>, val dye: DyeColor) : EditablePanel<PainterScreen>(this@PainterScreen, parent, width = 5f) {

View File

@ -0,0 +1,75 @@
package ru.dbotthepony.mc.otm.client.screen.matter
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
import ru.dbotthepony.mc.otm.client.screen.panels.SpritePanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu
class MatterEntanglerScreen(menu: MatterEntanglerMenu, inventory: Inventory, title: Component) : MatteryScreen<MatterEntanglerMenu>(menu, inventory, title) {
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
val frame = FramePanel(this, DEFAULT_FRAME_WIDTH, 110f, title)
makeBars(frame, profiledEnergy = menu.profiledEnergy, profiledMatter = menu.profiledMatter, batterySlot = menu.batterySlot)
GridPanel.slots(this, frame, 3, 3).also {
it.dock = Dock.LEFT
it.dockLeft = 20f
it.dockRight = 4f
it.dockResize = DockResizeMode.NONE
for (slot in menu.inputs)
SlotPanel(this, it, slot)
}
ProgressGaugePanel(this, frame, menu.progress).also {
it.dock = Dock.LEFT
it.dockHorizontal(4f)
it.dockResize = DockResizeMode.NONE
}
SlotPanel(this, frame, menu.outputs[0]).also {
it.dock = Dock.LEFT
it.dockHorizontal(4f)
it.dockResize = DockResizeMode.NONE
}
val strip = EditablePanel(this, frame, height = AbstractSlotPanel.SIZE)
strip.dock = Dock.BOTTOM
strip.dockTop = 4f
strip.childrenOrder = -1
SlotPanel(this, strip, menu.entanglingA).also {
it.dock = Dock.LEFT
it.dockLeft = 20f
}
SlotPanel(this, strip, menu.entanglingB).also {
it.dock = Dock.LEFT
it.dockLeft = 4f
}
SpritePanel(this, strip, ProgressGaugePanel.GAUGE_BACKGROUND).also {
it.dock = Dock.FILL
it.dockResize = DockResizeMode.NONE
}
SlotPanel(this, strip, menu.entanglingC).also {
it.dock = Dock.RIGHT
it.dockRight = 20f
}
DeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, upgrades = menu.upgrades, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig)
return frame
}
}

View File

@ -346,6 +346,10 @@ open class EditablePanel<out S : Screen> @JvmOverloads constructor(
}
}
fun dockHorizontal(value: Float) {
dockMargin = dockMargin.copy(left = value, right = value)
}
var dockTop: Float
get() = dockMargin.top
set(value) {

View File

@ -41,7 +41,7 @@ class EnergyCounterScreen(menu: EnergyCounterMenu, inventory: Inventory, title:
label.dock = Dock.TOP
}
if (!menu.ply.isSpectator) {
if (!menu.player.isSpectator) {
val button = ButtonPanel(this, frame, 0f, 0f, 0f, 20f, TranslatableComponent("block.overdrive_that_matters.energy_counter.switch"), onPress = Runnable { menu.switchDirection.accept(null) })
button.dock = Dock.TOP
button.setDockMargin(4f, 5f, 4f, 0f)

View File

@ -79,9 +79,28 @@ open class MatterGaugePanel<out S : Screen> @JvmOverloads constructor(
}
GAUGE_BACKGROUND.render(graphics)
val height = this.height * (1f - widget.percentage)
if (widget.percentage > 0.01f) {
renderLevel(graphics, 0f, 0f, width, height, widget.percentage, wavesStrength)
}
}
override fun innerRenderTooltips(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean {
if (isHovered) {
graphics.renderComponentTooltip(font, makeTooltip(), mouseX.toInt(), mouseY.toInt())
return true
}
return false
}
companion object {
val GAUGE_BACKGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(x = 18f, width = 9f)
val GAUGE_FOREGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(x = 27f, width = 9f)
fun renderLevel(graphics: GuiGraphics, x: Float, y: Float, width: Float, height: Float, percentage: Float, wavesStrength: Float = 0.5f) {
graphics.pose().pushPose()
graphics.pose().translate(x, y, 0f)
RenderSystem.setShader(GameRenderer::getPositionTexShader)
RenderSystem.enableBlend()
RenderSystem.defaultBlendFunc()
@ -98,15 +117,15 @@ open class MatterGaugePanel<out S : Screen> @JvmOverloads constructor(
builder.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_TEX)
builder.vertex(matrix, 0f, this.height, 0f).uv(u0, v1).endVertex()
builder.vertex(matrix, width, this.height, 0f).uv(u1, v1).endVertex()
builder.vertex(matrix, 0f, height, 0f).uv(u0, v1).endVertex()
builder.vertex(matrix, width, height, 0f).uv(u1, v1).endVertex()
for (i in 4 downTo 0) {
val sin = sin((System.currentTimeMillis() / 50L + i * 2L).toDouble() / 4.0).toFloat() * wavesStrength.coerceAtMost(4f)
val cos = cos((System.currentTimeMillis() / 50L + i * 3L + 27L).toDouble() / 4.0).toFloat() * wavesStrength.coerceAtMost(4f)
val thisX = (width * (i / 4f))
val thisY = (height + sin + cos).coerceAtLeast(0f)
val thisY = (height * (1f - percentage) + sin + cos).coerceAtLeast(0f)
builder.vertex(matrix, thisX, thisY, 0f).uv(
GAUGE_FOREGROUND.partialU((i / 4f) * GAUGE_FOREGROUND.width),
@ -115,22 +134,9 @@ open class MatterGaugePanel<out S : Screen> @JvmOverloads constructor(
}
BufferUploader.drawWithShader(builder.end())
graphics.pose().popPose()
}
}
override fun innerRenderTooltips(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean {
if (isHovered) {
graphics.renderComponentTooltip(font, makeTooltip(), mouseX.toInt(), mouseY.toInt())
return true
}
return false
}
companion object {
val GAUGE_BACKGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(x = 18f, width = 9f)
val GAUGE_FOREGROUND = WidgetLocation.VERTICAL_GAUGES.sprite(x = 27f, width = 9f)
}
}
private fun formatLevel(a: Decimal, b: Decimal): Component {

View File

@ -0,0 +1,50 @@
package ru.dbotthepony.mc.otm.compat.jei
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import ru.dbotthepony.mc.otm.client.ShiftPressedCond
import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.util.formatMatter
import ru.dbotthepony.mc.otm.core.util.formatPower
import ru.dbotthepony.mc.otm.systemTime
fun renderMatterGauge(
guiGraphics: GuiGraphics,
x: Float,
y: Float,
width: Float = MatterGaugePanel.GAUGE_BACKGROUND.width,
height: Float = MatterGaugePanel.GAUGE_BACKGROUND.height,
drainSpeed: Float = 1f,
wavesStrength: Float = drainSpeed * 2f,
) {
MatterGaugePanel.GAUGE_BACKGROUND.render(guiGraphics, x, y, width, height)
MatterGaugePanel.renderLevel(guiGraphics, x, y, width, height, 1f - ((systemTime.secondsF * drainSpeed) % 1f), wavesStrength)
}
fun renderEnergyGauge(
guiGraphics: GuiGraphics,
x: Float,
y: Float,
width: Float = PowerGaugePanel.GAUGE_BACKGROUND.width,
height: Float = PowerGaugePanel.GAUGE_BACKGROUND.height,
drainSpeed: Float = 1f
) {
val perc = 1f - ((systemTime.secondsF * drainSpeed) % 1f)
PowerGaugePanel.GAUGE_BACKGROUND.render(guiGraphics, x, y, width, height)
PowerGaugePanel.GAUGE_FOREGROUND.renderPartial(guiGraphics, x, y + height * (1f - perc) - 1f, width, height * perc)
}
fun matterGaugeTooltips(target: MutableList<Component>, matter: Decimal, mouseX: Double, mouseY: Double, x: Float, y: Float, width: Float = MatterGaugePanel.GAUGE_BACKGROUND.width, height: Float = MatterGaugePanel.GAUGE_BACKGROUND.height) {
if (mouseX in x .. (x + width - 1) && mouseY in y .. (y + height - 1)) {
target.add(TranslatableComponent("otm.gui.matter_panel.matter_required", matter.formatMatter(formatAsReadable = ShiftPressedCond)))
}
}
fun energyGaugeTooltips(target: MutableList<Component>, energy: Decimal, mouseX: Double, mouseY: Double, x: Float, y: Float, width: Float = PowerGaugePanel.GAUGE_BACKGROUND.width, height: Float = PowerGaugePanel.GAUGE_BACKGROUND.height) {
if (mouseX in x .. (x + width - 1) && mouseY in y .. (y + height - 1)) {
target.add(TranslatableComponent("otm.gui.energy_required", energy.formatPower(formatAsReadable = ShiftPressedCond)))
}
}

View File

@ -69,14 +69,17 @@ class JEIPlugin : IModPlugin {
registration.addRecipeCatalyst(ItemStack(MItems.POWERED_BLAST_FURNACE), RecipeTypes.BLASTING)
registration.addRecipeCatalyst(ItemStack(MItems.POWERED_SMOKER), RecipeTypes.SMOKING)
registration.addRecipeCatalyst(ItemStack(MItems.ExopackUpgrades.CRAFTING_UPGRADE), RecipeTypes.CRAFTING)
registration.addRecipeCatalyst(ItemStack(MItems.ITEM_MONITOR), RecipeTypes.CRAFTING)
registration.addRecipeCatalyst(ItemStack(MItems.PLATE_PRESS), PlatePressRecipeCategory.recipeType)
registration.addRecipeCatalyst(ItemStack(MItems.PAINTER), PainterRecipeCategory.recipeType)
registration.addRecipeCatalyst(ItemStack(MItems.MATTER_ENTANGLER), MatterEntanglerRecipeCategory.recipeType)
}
override fun registerCategories(registration: IRecipeCategoryRegistration) {
helpers = registration.jeiHelpers
registration.addRecipeCategories(PlatePressRecipeCategory)
registration.addRecipeCategories(PainterRecipeCategory)
registration.addRecipeCategories(MatterEntanglerRecipeCategory)
}
override fun registerRecipes(registration: IRecipeRegistration) {
@ -84,6 +87,7 @@ class JEIPlugin : IModPlugin {
registration.addRecipes(PlatePressRecipeCategory.recipeType, level.recipeManager.getAllRecipesFor(MRecipes.PLATE_PRESS).filter { !it.isIncomplete })
registration.addRecipes(PainterRecipeCategory.recipeType, level.recipeManager.getAllRecipesFor(MRecipes.PAINTER).filter { !it.isIncomplete })
registration.addRecipes(MatterEntanglerRecipeCategory.recipeType, level.recipeManager.getAllRecipesFor(MRecipes.MATTER_ENTANGLER).filter { !it.isIncomplete })
}
override fun registerRecipeTransferHandlers(registration: IRecipeTransferRegistration) {

View File

@ -0,0 +1,91 @@
package ru.dbotthepony.mc.otm.compat.jei
import mezz.jei.api.gui.builder.IRecipeLayoutBuilder
import mezz.jei.api.gui.drawable.IDrawable
import mezz.jei.api.gui.ingredient.IRecipeSlotsView
import mezz.jei.api.recipe.IFocusGroup
import mezz.jei.api.recipe.RecipeIngredientRole
import mezz.jei.api.recipe.RecipeType
import mezz.jei.api.recipe.category.IRecipeCategory
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.ItemStackIcon
import ru.dbotthepony.mc.otm.client.render.draw
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.recipe.IMatterEntanglerRecipe
import ru.dbotthepony.mc.otm.registry.MItems
import ru.dbotthepony.mc.otm.registry.MNames
object MatterEntanglerRecipeCategory : IRecipeCategory<IMatterEntanglerRecipe>, IDrawable {
private val recipeType = RecipeType.create(OverdriveThatMatters.MOD_ID, MNames.MATTER_ENTANGLER, IMatterEntanglerRecipe::class.java)
override fun getRecipeType(): RecipeType<IMatterEntanglerRecipe> {
return recipeType
}
override fun getTitle(): Component {
return MItems.MATTER_ENTANGLER.description
}
override fun getBackground(): IDrawable {
return this
}
override fun getWidth(): Int {
return 140
}
override fun getHeight(): Int {
return 60
}
private val icon = IGUIRenderable2IDrawable(ItemStackIcon(ItemStack(MItems.MATTER_ENTANGLER)))
override fun getIcon(): IDrawable {
return icon
}
override fun setRecipe(builder: IRecipeLayoutBuilder, recipe: IMatterEntanglerRecipe, focuses: IFocusGroup) {
for (x in 0 until recipe.ingredients.width) {
for (y in 0 until recipe.ingredients.height) {
builder.addSlot(RecipeIngredientRole.INPUT, 30 + x * 18, 4 + y * 18).addIngredients(recipe.ingredients[x, y])
}
}
builder.addSlot(RecipeIngredientRole.OUTPUT, 116, 18 + 4).addItemStack(recipe.result)
}
override fun draw(guiGraphics: GuiGraphics, xOffset: Int, yOffset: Int) {
for (x in 0 until 3) {
for (y in 0 until 3) {
AbstractSlotPanel.SLOT_BACKGROUND.render(guiGraphics, xOffset + x * 18f + 29f, yOffset + y * 18f + 3f)
}
}
AbstractSlotPanel.SLOT_BACKGROUND.render(guiGraphics, xOffset + 115f, yOffset + 18f + 3f)
ProgressGaugePanel.GAUGE_BACKGROUND.render(guiGraphics, xOffset + 89f, yOffset + 18f + 4f)
}
override fun draw(recipe: IMatterEntanglerRecipe, recipeSlotsView: IRecipeSlotsView, guiGraphics: GuiGraphics, mouseX: Double, mouseY: Double) {
renderMatterGauge(guiGraphics, 13f, 6f, drainSpeed = (recipe.matter / Decimal(300)).toFloat())
renderEnergyGauge(guiGraphics, 4f, 6f, drainSpeed = (recipe.ticks / 2000.0).toFloat())
guiGraphics.draw(minecraft.font, x = 85f, y = 45f, text = TranslatableComponent("otm.gui.recipe.ticks", recipe.ticks), drawShadow = true)
}
override fun getTooltipStrings(recipe: IMatterEntanglerRecipe, recipeSlotsView: IRecipeSlotsView, mouseX: Double, mouseY: Double): MutableList<Component> {
val result = ArrayList<Component>()
matterGaugeTooltips(result, recipe.matter, mouseX, mouseY, 13f, 6f)
energyGaugeTooltips(result, MachinesConfig.MATTER_ENTANGLER.energyConsumption * recipe.ticks * MachinesConfig.MATTER_ENTANGLER.workTimeMultiplier, mouseX, mouseY, 4f, 6f)
return result
}
}

View File

@ -43,8 +43,10 @@ object PainterRecipeCategory : IRecipeCategory<PainterRecipe>, IDrawable {
return 56
}
private val icon = IGUIRenderable2IDrawable(ItemStackIcon(ItemStack(MItems.PAINTER)))
override fun getIcon(): IDrawable {
return IGUIRenderable2IDrawable(ItemStackIcon(ItemStack(MItems.PAINTER)))
return icon
}
override fun setRecipe(builder: IRecipeLayoutBuilder, recipe: PainterRecipe, focuses: IFocusGroup) {

View File

@ -13,6 +13,14 @@ object MachinesConfig : AbstractConfig("machines") {
energyConsumption = Decimal(15)
)
val MATTER_ENTANGLER = workerValues(
MNames.MATTER_ENTANGLER,
energyStorage = Decimal(40_000),
energyThroughput = Decimal(400),
energyConsumption = Decimal(120),
matterCapacity = Decimal(60),
)
val MATTER_DECOMPOSER = workerValues(
MNames.MATTER_DECOMPOSER,
energyStorage = Decimal(100_000),

View File

@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.ints.IntSet
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
import net.minecraft.world.Container
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.enchantment.EnchantmentHelper.hasVanishingCurse
import net.minecraftforge.fluids.capability.IFluidHandler
@ -245,3 +246,14 @@ fun Container.balance() {
balance(IntArraySet(containerSize).also { for (i in 0 until containerSize) it.add(i) }, false)
}
operator fun CraftingContainer.get(column: Int, row: Int): ItemStack {
return getItem(column + row * width)
}
operator fun CraftingContainer.get(column: Int, row: Int, flop: Boolean): ItemStack {
return if (flop)
get(width - column - 1, row)
else
get(column, row)
}

View File

@ -4,7 +4,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.ints.IntComparators
import it.unimi.dsi.fastutil.objects.ObjectIterators
import it.unimi.dsi.fastutil.ints.IntSpliterator
import it.unimi.dsi.fastutil.objects.ObjectSpliterators
import net.minecraft.world.item.ItemStack
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
@ -13,11 +14,16 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.world.Container
import kotlin.jvm.JvmOverloads
import net.minecraft.world.entity.player.Player
import net.minecraft.world.entity.player.StackedContents
import net.minecraft.world.inventory.StackedContentsCompatible
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.registries.ForgeRegistries
import ru.dbotthepony.mc.otm.core.addSorted
import ru.dbotthepony.mc.otm.core.collect.any
import ru.dbotthepony.mc.otm.core.collect.count
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set
@ -26,17 +32,18 @@ import ru.dbotthepony.mc.otm.core.util.ItemValueCodec
import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec
import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer
import ru.dbotthepony.mc.otm.network.synchronizer.IField
import java.lang.ref.PhantomReference
import java.lang.ref.ReferenceQueue
import java.lang.ref.WeakReference
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
import java.util.function.Consumer
import java.util.function.Predicate
import java.util.function.Supplier
import java.util.stream.Stream
import java.util.stream.StreamSupport
import kotlin.NoSuchElementException
import kotlin.collections.ArrayList
@Suppress("UNUSED")
open class MatteryContainer(protected val watcher: Runnable, private val size: Int) : Container, Iterable<ItemStack>, INBTSerializable<Tag?> {
open class MatteryContainer(protected val watcher: Runnable, private val size: Int) : Container, Iterable<ItemStack>, INBTSerializable<Tag?>, StackedContentsCompatible {
constructor(size: Int) : this({}, size)
init {
@ -440,6 +447,12 @@ open class MatteryContainer(protected val watcher: Runnable, private val size: I
}
}
override fun fillStackedContents(contents: StackedContents) {
for (item in iterator()) {
contents.accountStack(item)
}
}
final override fun removeItem(slot: Int, amount: Int): ItemStack {
if (amount <= 0 || slot < 0 || slot >= size || slots[slot].isEmpty)
return ItemStack.EMPTY
@ -576,6 +589,26 @@ open class MatteryContainer(protected val watcher: Runnable, private val size: I
}
}
private inner class Spliterator(private val parent: IntSpliterator) : java.util.Spliterator<ItemStack> {
override fun tryAdvance(action: Consumer<in ItemStack>): Boolean {
return parent.tryAdvance {
action.accept(getItem(it))
}
}
override fun trySplit(): java.util.Spliterator<ItemStack>? {
return parent.trySplit()?.let(::Spliterator)
}
override fun estimateSize(): Long {
return parent.estimateSize()
}
override fun characteristics(): Int {
return parent.characteristics()
}
}
private object EmptyIterator : IContainerIterator {
override fun hasNext(): Boolean {
return false
@ -601,4 +634,29 @@ open class MatteryContainer(protected val watcher: Runnable, private val size: I
return Iterator()
}
final override fun countItem(item: Item): Int {
return iterator().filter { it.item == item }.count().toInt()
}
final override fun hasAnyOf(set: Set<Item>): Boolean {
return iterator().any { it.item in set }
}
final override fun hasAnyMatching(predicate: Predicate<ItemStack>): Boolean {
return iterator().any(predicate)
}
final override fun spliterator(): java.util.Spliterator<ItemStack> {
if (isEmpty) {
return ObjectSpliterators.emptySpliterator()
}
indicesReferenced = true
return Spliterator(nonEmptyIndices.intSpliterator())
}
fun stream(): Stream<ItemStack> {
return StreamSupport.stream(spliterator(), false)
}
}

View File

@ -0,0 +1,22 @@
package ru.dbotthepony.mc.otm.container
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.ItemStack
open class MatteryCraftingContainer(watcher: Runnable, private val width: Int, private val height: Int) : MatteryContainer(watcher, width * height), CraftingContainer {
constructor(width: Int, height: Int) : this({}, width, height)
final override fun getWidth(): Int {
return width
}
final override fun getHeight(): Int {
return height
}
final override fun getItems(): MutableList<ItemStack> {
val i = spliterator()
val result = ArrayList<ItemStack>(i.estimateSize().toInt())
i.forEachRemaining { result.add(it) }
return result
}
}

View File

@ -0,0 +1,48 @@
package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap
import net.minecraft.world.Container
import net.minecraft.world.item.ItemStack
import java.util.Arrays
class ShadowContainer(private val parent: Container) : Container by parent {
private val shadowed = Int2ObjectArrayMap<ItemStack>(0)
override fun clearContent() {
shadowed.clear()
parent.clearContent()
}
override fun isEmpty(): Boolean {
return parent.isEmpty && shadowed.isEmpty()
}
override fun getItem(slot: Int): ItemStack {
return shadowed[slot] ?: parent.getItem(slot)
}
override fun removeItem(slot: Int, count: Int): ItemStack {
val shadow = shadowed[slot] ?: return parent.removeItem(slot, count)
val copy = shadow.copyWithCount(shadow.count.coerceAtLeast(count))
shadow.split(count)
if (shadow.isEmpty) shadowed[slot] = ItemStack.EMPTY
return copy
}
override fun removeItemNoUpdate(slot: Int): ItemStack {
shadowed[slot] ?: return parent.removeItemNoUpdate(slot)
val old = shadowed[slot]
shadowed[slot] = ItemStack.EMPTY
return old!!
}
override fun setItem(slot: Int, item: ItemStack) {
shadowed[slot] = item
}
companion object {
fun shadow(container: Container, slot: Int, itemStack: ItemStack): Container {
return ShadowContainer(container).also { it[slot] = itemStack }
}
}
}

View File

@ -0,0 +1,33 @@
package ru.dbotthepony.mc.otm.container
import net.minecraft.world.Container
import net.minecraft.world.entity.player.StackedContents
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.collect.toList
class ShadowCraftingContainer(private val parent: CraftingContainer) : Container by ShadowContainer(parent), CraftingContainer {
override fun fillStackedContents(contents: StackedContents) {
for (item in iterator()) {
contents.accountStack(item)
}
}
override fun getWidth(): Int {
return parent.width
}
override fun getHeight(): Int {
return parent.height
}
override fun getItems(): MutableList<ItemStack> {
return iterator().toList()
}
companion object {
fun shadow(container: CraftingContainer, slot: Int, itemStack: ItemStack): CraftingContainer {
return ShadowCraftingContainer(container).also { it[slot] = itemStack }
}
}
}

View File

@ -3,9 +3,14 @@ package ru.dbotthepony.mc.otm.core.collect
import it.unimi.dsi.fastutil.objects.ObjectIterators
import ru.dbotthepony.mc.otm.core.addAll
import java.util.Optional
import java.util.Spliterator
import java.util.Spliterators
import java.util.function.BinaryOperator
import java.util.function.Predicate
import java.util.function.Supplier
import java.util.stream.Collector
import java.util.stream.Stream
import java.util.stream.StreamSupport
// Purpose of Stream API over Iterators is that it is simple enough for JIT to inline most of it,
// unlike actual Streams.
@ -370,3 +375,29 @@ fun <T> Iterator<T>.maybe(): T? {
fun <T> emptyIterator(): MutableIterator<T> {
return ObjectIterators.emptyIterator()
}
fun <T> Iterator<T>.toStream(): Stream<T> {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(this, 0), false)
}
fun <T> Iterator<T>.allEqual(): Boolean {
if (hasNext()) {
val v = next()
while (hasNext()) {
if (v != next()) {
return false
}
}
return true
}
return false
}
fun <T> Iterator<T>.count(): Long {
var count = 0L
while (hasNext()) count++
return count
}

View File

@ -17,19 +17,64 @@ import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.util.readBinaryJsonWithCodecIndirect
import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec
import java.util.*
import kotlin.NoSuchElementException
import kotlin.concurrent.getOrSet
import kotlin.collections.ArrayDeque
class Codec2RecipeSerializer<S : Recipe<*>>(val empty: S?, val codec: Codec<S>) : Codec<S> by codec, RecipeSerializer<S> {
constructor(supplier: (() -> ResourceLocation) -> Codec<S>) : this(null, supplier.invoke(::context))
constructor(empty: S, supplier: (() -> ResourceLocation) -> Codec<S>) : this(empty, supplier.invoke(::context))
class Codec2RecipeSerializer<S : Recipe<*>> private constructor(
val empty: S?,
private val id: ArrayDeque<ResourceLocation>,
codec: (Codec2RecipeSerializer<S>.Context) -> Codec<S>,
) : Codec<S>, RecipeSerializer<S> {
constructor(empty: S?, codec: (Codec2RecipeSerializer<S>.Context) -> Codec<S>) : this(empty, ArrayDeque(), codec)
constructor(supplier: (Codec2RecipeSerializer<S>.Context) -> Codec<S>) : this(null, supplier)
override fun fromJson(p_44103_: ResourceLocation, p_44104_: JsonObject): S {
private val codec = codec.invoke(Context())
inner class Context() {
val id: ResourceLocation
get() = checkNotNull(this@Codec2RecipeSerializer.id.lastOrNull()) { "Not currently deserializing recipe" }
fun <O : Recipe<*>> wrap(other: Codec2RecipeSerializer<O>): Codec<O> {
return object : Codec<O> {
override fun <T : Any> encode(input: O, ops: DynamicOps<T>, prefix: T): DataResult<T> {
try {
other.id.addLast(this@Context.id)
return other.encode(input, ops, prefix)
} finally {
other.id.removeLast()
}
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<O, T>> {
try {
other.id.addLast(this@Context.id)
return other.decode(ops, input)
} finally {
other.id.removeLast()
}
}
}
}
}
override fun <T : Any> encode(input: S, ops: DynamicOps<T>, prefix: T): DataResult<T> {
return codec.encode(input, ops, prefix)
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<S, T>> {
return codec.decode(ops, input)
}
fun <O : Recipe<*>> xmap(to: (S) -> O, from: (O) -> S): Codec2RecipeSerializer<O> {
return Codec2RecipeSerializer(empty?.let(to), id) { _ ->
codec.xmap(to, from)
}
}
override fun fromJson(id: ResourceLocation, data: JsonObject): S {
try {
deck.getOrSet(::LinkedList).addLast(p_44103_)
this.id.addLast(id)
return codec.decode(JsonOps.INSTANCE, p_44104_).get().map(
return decode(JsonOps.INSTANCE, data).get().map(
{
it.first
},
@ -38,29 +83,29 @@ class Codec2RecipeSerializer<S : Recipe<*>>(val empty: S?, val codec: Codec<S>)
}
)
} finally {
deck.get().removeLast()
this.id.removeLast()
}
}
override fun fromNetwork(p_44105_: ResourceLocation, p_44106_: FriendlyByteBuf): S? {
override fun fromNetwork(id: ResourceLocation, data: FriendlyByteBuf): S? {
try {
deck.getOrSet(::LinkedList).addLast(p_44105_)
this.id.addLast(id)
return p_44106_.readBinaryJsonWithCodecIndirect(codec)
.resultOrPartial { LOGGER.error("Failed to read recipe $p_44105_ from network: $it") }.orElse(null)
return data.readBinaryJsonWithCodecIndirect(this)
.resultOrPartial { LOGGER.error("Failed to read recipe $id from network: $it") }.orElse(null)
} finally {
deck.get().removeLast()
this.id.removeLast()
}
}
override fun toNetwork(p_44101_: FriendlyByteBuf, p_44102_: S) {
p_44101_.writeBinaryJsonWithCodec(codec, p_44102_)
override fun toNetwork(data: FriendlyByteBuf, recipe: S) {
data.writeBinaryJsonWithCodec(this, recipe)
}
fun toFinished(recipe: S): FinishedRecipe {
return object : FinishedRecipe {
override fun serializeRecipeData(p_125967_: JsonObject) {
codec.encode(recipe, JsonOps.INSTANCE, p_125967_).get().map(
encode(recipe, JsonOps.INSTANCE, p_125967_).get().map(
{
it as JsonObject
@ -92,33 +137,7 @@ class Codec2RecipeSerializer<S : Recipe<*>>(val empty: S?, val codec: Codec<S>)
}
}
companion object : Codec<ResourceLocation> {
private val deck = ThreadLocal<LinkedList<ResourceLocation>>()
private fun context(): ResourceLocation {
val deck = deck.getOrSet(::LinkedList)
if (deck.isEmpty()) {
throw NoSuchElementException("Context stack is empty")
} else {
return deck.last
}
}
override fun <T : Any> encode(input: ResourceLocation, ops: DynamicOps<T>, prefix: T): DataResult<T> {
return DataResult.success(ops.empty())
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<ResourceLocation, T>> {
val deck = deck.getOrSet(::LinkedList)
if (deck.isEmpty()) {
return DataResult.error { "Attempt to use recipe serializer codec ResourceLocation' hack outside Codec2RecipeSerializer" }
} else {
return DataResult.success(Pair(deck.last, ops.empty()))
}
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -0,0 +1,111 @@
package ru.dbotthepony.mc.otm.data
import com.mojang.datafixers.util.Pair
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.JsonOps
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.world.item.crafting.Ingredient
import ru.dbotthepony.mc.otm.core.collect.allEqual
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.collect.toStream
import ru.dbotthepony.mc.otm.core.stream
import ru.dbotthepony.mc.otm.recipe.IIngredientMatrix
import ru.dbotthepony.mc.otm.recipe.IngredientMatrix
import java.util.function.Supplier
object IngredientMatrixCodec : Codec<IIngredientMatrix> {
private val ingredientList = Codec.list(IngredientCodec)
override fun <T : Any> encode(input: IIngredientMatrix, ops: DynamicOps<T>, prefix: T): DataResult<T> {
return DataResult.success(
ops.createList(
(0 until input.height).stream().map { row ->
ops.createList((0 until input.width).stream().map { column ->
JsonOps.INSTANCE.convertTo(ops, input[column, row].toJson())
})
}
)
)
}
private data class Handwritten(val pattern: List<String>, val key: Map<Char, Ingredient>)
private val handwrittenCodec = RecordCodecBuilder.create<Handwritten> {
it.group(
Codec.list(Codec.STRING)
.flatXmap(
{ DataResult.success(it) },
{ if (it.iterator().map { it.length }.allEqual()) DataResult.success(it) else DataResult.error { "One or more of patten strings differ in length" } }
)
.fieldOf("pattern").forGetter(Handwritten::pattern),
Codec.unboundedMap(
Codec.STRING
.flatXmap(
{ if (it.length == 1) DataResult.success(it[0]) else DataResult.error { "Ingredient key must be exactly 1 symbol in length, '$it' is invalid" } },
{ DataResult.success(it.toString()) }
), IngredientCodec).fieldOf("key").forGetter(Handwritten::key)
).apply(it, ::Handwritten)
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<IIngredientMatrix, T>> {
return ops.getList(input).get().map(
{
val lines = ArrayList<DataResult<List<Ingredient>>>()
it.accept {
lines.add(ingredientList.decode(ops, it).map { it.first })
}
val errors = ArrayList<Supplier<String>>()
val ingredients = ArrayList<List<Ingredient>>()
lines.withIndex().forEach {
val (line, result) = it
result.get().map({ ingredients.add(it) }, { errors.add { "Line $line: ${it.message()}" } })
}
if (errors.isNotEmpty()) {
DataResult.error { "Failed to decode ingredient matrix: ${errors.joinToString { it.get() }}" }
} else if (ingredients.isEmpty()) {
DataResult.error { "Ingredient list is empty" }
} else if (!ingredients.iterator().map { it.size }.allEqual()) {
DataResult.error { "Ingredient list is not a matrix (one or multiple of rows are mismatched size)" }
} else {
val result = IngredientMatrix(ingredients.first().size, ingredients.size)
for ((row, columns) in ingredients.withIndex()) {
for ((column, ingredient) in columns.withIndex()) {
result[column, row] = ingredient
}
}
DataResult.success(Pair(result, ops.empty()))
}
},
{ err1 ->
handwrittenCodec.decode(ops, input).get().map(
{
DataResult.success(it)
},
{
DataResult.error { "Failed to decode ingredients as list: ${err1.message()} and as pattern/dictionary: ${it.message()}" }
}
).flatMap {
val handwritten = it.first
val result = IngredientMatrix(handwritten.pattern.first().length, handwritten.pattern.size)
for ((row, pattern) in handwritten.pattern.withIndex()) {
for ((column, symbol) in pattern.withIndex()) {
val ingredient = if (symbol == ' ') handwritten.key[symbol] ?: Ingredient.EMPTY else handwritten.key[symbol] ?: return@flatMap DataResult.error { "Unknown ingredient with index '$symbol'" }
result[column, row] = ingredient
}
}
DataResult.success(Pair(result, ops.empty()))
}
}
)
}
}

View File

@ -0,0 +1,7 @@
package ru.dbotthepony.mc.otm.item
import net.minecraft.world.item.ItemStack
interface IQuantumLinked {
fun merge(from: ItemStack, into: ItemStack): ItemStack
}

View File

@ -63,7 +63,7 @@ import kotlin.collections.forEach
import kotlin.collections.iterator
import kotlin.collections.set
class QuantumBatteryItem(val savedataID: String, val balanceValues: EnergyBalanceValues?) : Item(Properties().stacksTo(1).rarity(if (balanceValues == null) Rarity.EPIC else Rarity.UNCOMMON)) {
class QuantumBatteryItem(val savedataID: String, val balanceValues: EnergyBalanceValues?) : Item(Properties().stacksTo(1).rarity(if (balanceValues == null) Rarity.EPIC else Rarity.UNCOMMON)), IQuantumLinked {
val isCreative = balanceValues == null
interface IValues {
@ -169,9 +169,9 @@ class QuantumBatteryItem(val savedataID: String, val balanceValues: EnergyBalanc
fun updateValues() {
if (!values.isServer && isServerThread()) {
values = serverData.values(stack.tag?.getUUIDSafe("id") ?: UUID.randomUUID().also { stack.tagNotNull["id"] = it })
values = serverData.values(stack.tag?.getUUIDSafe("uuid") ?: UUID.randomUUID().also { stack.tagNotNull["uuid"] = it })
} else if (isClientThread()) {
val id = stack.tag?.getUUIDSafe("id") ?: return
val id = stack.tag?.getUUIDSafe("uuid") ?: return
if (values.uuid != id)
values = clientData.computeIfAbsent(id, Function { UnboundValues(it) })
@ -278,6 +278,10 @@ class QuantumBatteryItem(val savedataID: String, val balanceValues: EnergyBalanc
get() = if (isCreative) Decimal.LONG_MAX_VALUE else super.missingPower
}
override fun merge(from: ItemStack, into: ItemStack): ItemStack {
return from.copyWithCount(from.count + into.count)
}
override fun isBarVisible(p_150899_: ItemStack): Boolean {
if (isCreative) return false
return p_150899_.matteryEnergy != null

View File

@ -69,7 +69,7 @@ class ExopackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMen
}
val armorSlots = makeArmorSlots()
val curiosSlots: ImmutableList<PlayerSlot<Slot, Slot>> = ImmutableList.copyOf(ply.curiosSlots)
val curiosSlots: ImmutableList<PlayerSlot<Slot, Slot>> = ImmutableList.copyOf(player.curiosSlots)
init {
for ((a, b) in curiosSlots) {
@ -84,10 +84,10 @@ class ExopackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMen
}
private fun popFurnaceExp() {
if (capability.isExopackSmeltingInstalled && capability.exopackSmelterExperience >= 1f && !ply.level().isClientSide) {
if (capability.isExopackSmeltingInstalled && capability.exopackSmelterExperience >= 1f && !player.level().isClientSide) {
val whole = capability.exopackSmelterExperience.toInt()
capability.exopackSmelterExperience -= whole
ExperienceOrb.award(ply.level() as ServerLevel, ply.position(), whole)
ExperienceOrb.award(player.level() as ServerLevel, player.position(), whole)
}
}
@ -99,8 +99,8 @@ class ExopackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMen
}
}
val furnaceOutputs: List<MachineOutputSlot> = capability.smelters.map {
object : MachineOutputSlot(it.output, 0, onTake = { popFurnaceExp() }) {
val furnaceOutputs: List<OutputSlot> = capability.smelters.map {
object : OutputSlot(it.output, 0, onTake = { popFurnaceExp() }) {
override fun mayPickup(player: Player): Boolean {
return super.mayPickup(player) && capability.isExopackSmeltingInstalled
}
@ -120,7 +120,7 @@ class ExopackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMen
init {
if (capability.isExopackEnderAccessInstalled) {
enderChestSlots = makeSlots(ply.enderChestInventory) { a, b ->
enderChestSlots = makeSlots(player.enderChestInventory) { a, b ->
MatterySlot(a, b).also {
addStorageSlot(it, condition = enderChestOpenState)
}
@ -238,16 +238,16 @@ class ExopackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMen
}
fun sendInitialData(container: ExopackInventoryMenu, itemStacks: NonNullList<ItemStack>, carried: ItemStack, remoteDataSlots: IntArray) {
MatteryPlayerNetworkChannel.send(container.ply as ServerPlayer, ExopackMenuInitPacket(itemStacks, carried, container.incrementStateId()))
MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackMenuInitPacket(itemStacks, carried, container.incrementStateId()))
}
fun sendSlotChange(container: ExopackInventoryMenu, slotId: Int, itemStack: ItemStack) {
if (container.slots[slotId].container != container.ply.inventory || container.ply.containerMenu is ExopackInventoryMenu)
MatteryPlayerNetworkChannel.send(container.ply as ServerPlayer, ExopackSlotPacket(slotId, itemStack, container.stateId))
if (container.slots[slotId].container != container.player.inventory || container.player.containerMenu is ExopackInventoryMenu)
MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackSlotPacket(slotId, itemStack, container.stateId))
}
fun sendCarriedChange(container: ExopackInventoryMenu, itemStack: ItemStack) {
MatteryPlayerNetworkChannel.send(container.ply as ServerPlayer, ExopackCarriedPacket(itemStack, container.stateId))
MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackCarriedPacket(itemStack, container.stateId))
}
fun sendDataChange(container: ExopackInventoryMenu, dataSlotId: Int, shortData: Int) {

View File

@ -101,7 +101,7 @@ abstract class MatteryMenu(
* Server->Client synchronizer
*/
val mSynchronizer = FieldSynchronizer()
val ply: Player get() = inventory.player
val player: Player get() = inventory.player
private val _playerInventorySlots = ArrayList<InventorySlot>()
private val _playerHotbarSlots = ArrayList<InventorySlot>()
@ -229,7 +229,7 @@ abstract class MatteryMenu(
protected var inventorySlotIndexStart = 0
protected var inventorySlotIndexEnd = 0
private val playerPacketDistributor = PacketDistributor.PLAYER.with { ply as ServerPlayer }
private val playerPacketDistributor = PacketDistributor.PLAYER.with { player as ServerPlayer }
fun addFilterSlots(slots: ItemFilter): List<GetterSetter<ItemStack>> {
val result = ArrayList<GetterSetter<ItemStack>>(slots.size)
@ -277,8 +277,8 @@ abstract class MatteryMenu(
}
override fun isSameInventory(other: Slot): Boolean {
if (container === inventory || container === ply.matteryPlayer?.exopackContainer)
return (other.container === inventory || other.container === ply.matteryPlayer?.exopackContainer) && isSameFilter(other)
if (container === inventory || container === player.matteryPlayer?.exopackContainer)
return (other.container === inventory || other.container === player.matteryPlayer?.exopackContainer) && isSameFilter(other)
return super.isSameInventory(other)
}
@ -287,7 +287,7 @@ abstract class MatteryMenu(
private set
init {
val mattery = ply.matteryPlayer
val mattery = player.matteryPlayer
if (mattery != null) {
if (container === inventory) {
@ -349,7 +349,7 @@ abstract class MatteryMenu(
}
}
protected fun addInventorySlots(autoFrame: Boolean = !ply.isSpectator) {
protected fun addInventorySlots(autoFrame: Boolean = !player.isSpectator) {
check(_playerInventorySlots.isEmpty()) { "Already created inventory slots" }
autoCreateInventoryFrame = autoFrame
@ -378,7 +378,7 @@ abstract class MatteryMenu(
addSlot(slot)
}
val mattery = ply.matteryPlayer
val mattery = player.matteryPlayer
if (mattery != null && mattery.hasExopack) {
for (i in 0 until mattery.exopackContainer.containerSize) {
@ -415,9 +415,9 @@ abstract class MatteryMenu(
if (payload != null) {
if (broadcastOnce) {
MenuNetworkChannel.send(ply, MenuFieldPacket(containerId, payload))
MenuNetworkChannel.send(player, MenuFieldPacket(containerId, payload))
} else {
MenuNetworkChannel.sendNow(ply, MenuFieldPacket(containerId, payload))
MenuNetworkChannel.sendNow(player, MenuFieldPacket(containerId, payload))
}
}
@ -449,7 +449,7 @@ abstract class MatteryMenu(
fun syncCarried() {
setRemoteCarried(carried.copy())
MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(carried))
MenuNetworkChannel.send(player as ServerPlayer, SetCarriedPacket(carried))
}
fun syncCarried(stack: ItemStack) {
@ -635,11 +635,11 @@ abstract class MatteryMenu(
if (remainder.isEmpty) {
source.set(ItemStack.EMPTY)
source.onTake(ply, copy)
source.onTake(player, copy)
} else {
copy.count = source.item.count - remainder.count
source.item.count = remainder.count
source.onTake(ply, copy)
source.onTake(player, copy)
}
return true
@ -745,7 +745,7 @@ abstract class MatteryMenu(
return armorSlots!!
}
val cosmetic = ply.cosmeticArmorSlots
val cosmetic = player.cosmeticArmorSlots
return ImmutableList.of(
PlayerSlot(EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.HEAD), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.HEAD)),
@ -780,7 +780,7 @@ abstract class MatteryMenu(
fun makeEquipmentSlots(mapMoveToExternal: Boolean = false): EquipmentSlots {
return EquipmentSlots(
armorSlots = makeArmorSlots(mapMoveToExternal),
curiosSlots = curiosSlots ?: ImmutableList.copyOf(ply.curiosSlots).also {
curiosSlots = curiosSlots ?: ImmutableList.copyOf(player.curiosSlots).also {
for ((a, b) in it) {
equipmentSlots.add(a)
addSlot(a)

View File

@ -11,7 +11,6 @@ import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.container.UpgradeContainer
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.runOnClient
@ -80,7 +79,7 @@ open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int
}
}
open class MachineOutputSlot(container: Container, index: Int, x: Int = 0, y: Int = 0, val onTake: (ItemStack) -> Unit = {}) : MatterySlot(container, index, x, y) {
open class OutputSlot(container: Container, index: Int, x: Int = 0, y: Int = 0, val onTake: (ItemStack) -> Unit = {}) : MatterySlot(container, index, x, y) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return false
}

View File

@ -8,8 +8,7 @@ import net.minecraftforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting
import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
import ru.dbotthepony.mc.otm.capability.isNotEmpty
import ru.dbotthepony.mc.otm.capability.isNotFull
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
import ru.dbotthepony.mc.otm.menu.OutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
@ -50,7 +49,7 @@ class FluidTankMenu(containerId: Int, inventory: Inventory, tile: FluidTankBlock
}
}
val output = MachineOutputSlot(tile?.output ?: SimpleContainer(1), 0)
val output = OutputSlot(tile?.output ?: SimpleContainer(1), 0)
init {
// сначала слот на заполнение из бака

View File

@ -1,4 +1,4 @@
package ru.dbotthepony.mc.otm.menu.tech
package ru.dbotthepony.mc.otm.menu.decorative
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory

View File

@ -7,10 +7,8 @@ import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget
import net.minecraft.world.SimpleContainer
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.core.orNull
import ru.dbotthepony.mc.otm.matter.MatterManager
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
import ru.dbotthepony.mc.otm.menu.OutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
@ -27,8 +25,8 @@ class MatterDecomposerMenu @JvmOverloads constructor(
override fun mayPlace(itemStack: ItemStack) = MatterManager.canDecompose(itemStack)
}
val outputMain: MachineOutputSlot
val outputStacking: MachineOutputSlot
val outputMain: OutputSlot
val outputStacking: OutputSlot
val progressWidget = ProgressGaugeWidget(this, tile)
val matterWidget = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter))
val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig)
@ -40,8 +38,8 @@ class MatterDecomposerMenu @JvmOverloads constructor(
val container = tile?.outputContainer ?: SimpleContainer(2)
// Выход
outputMain = MachineOutputSlot(container, 0)
outputStacking = MachineOutputSlot(container, 1)
outputMain = OutputSlot(container, 0)
outputStacking = OutputSlot(container, 1)
addStorageSlot(outputMain)
addStorageSlot(outputStacking)

View File

@ -0,0 +1,130 @@
package ru.dbotthepony.mc.otm.menu.matter
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.block.entity.matter.MatterEntanglerBlockEntity
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer
import ru.dbotthepony.mc.otm.container.ShadowCraftingContainer
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.container.set
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.item.IQuantumLinked
import ru.dbotthepony.mc.otm.menu.OutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.makeSlots
import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget
import ru.dbotthepony.mc.otm.registry.MMenus
import ru.dbotthepony.mc.otm.registry.MRecipes
import java.util.*
class MatterEntanglerMenu(
containerId: Int, inventory: Inventory, tile: MatterEntanglerBlockEntity? = null
) : MatteryPoweredMenu(MMenus.MATTER_ENTANGLER, containerId, inventory, tile) {
val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig)
val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy)
val profiledMatter = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter))
val progress = ProgressGaugeWidget(this, tile?.jobEventLoops?.get(0))
val inputs: List<MatterySlot> = makeSlots(tile?.inputs ?: object : MatteryCraftingContainer(3, 3) {
override fun getMaxStackSize(): Int {
return 1
}
}) { it, i ->
object : MatterySlot(it, i) {
override fun mayPlace(itemStack: ItemStack): Boolean {
val shadow = ShadowCraftingContainer.shadow(it, i, itemStack)
val level = player.level()
return super.mayPlace(itemStack) && (level ?: return false)
.recipeManager
.byType(MRecipes.MATTER_ENTANGLER)
.values
.any { it.preemptivelyMatches(shadow, level) }
}
}
}
val outputs = makeSlots(tile?.output ?: SimpleContainer(1), ::OutputSlot)
val upgrades = makeUpgradeSlots(3, tile?.upgrades)
private val entangling: Container = if (tile == null) object : SimpleContainer(2) {
override fun getMaxStackSize(): Int {
return 1
}
} else object : MatteryContainer(::rescan, 2) {
override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int {
return 1
}
}
private fun rescan() {
if (player is ServerPlayer && entangling[0].item is IQuantumLinked && entangling[1].item == entangling[0].item && entangling[0].isNotEmpty && entangling[1].isNotEmpty) {
entanglingC.container[0] = (entangling[0].item as IQuantumLinked).merge(entangling[0], entangling[1])
} else if (player is ServerPlayer) {
entanglingC.container[0] = ItemStack.EMPTY
}
}
private inner class EntanglingInputSlot(index: Int) : MatterySlot(entangling, index) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return super.mayPlace(itemStack) && itemStack.item is IQuantumLinked
}
}
val entanglingA: MatterySlot = EntanglingInputSlot(0)
val entanglingB: MatterySlot = EntanglingInputSlot(1)
val entanglingC: MatterySlot = object : MatterySlot(SimpleContainer(1), 0) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return false
}
override fun canTakeItemForPickAll(): Boolean {
return false
}
override fun tryRemove(p_150642_: Int, p_150643_: Int, p_150644_: Player): Optional<ItemStack> {
rescan()
return super.tryRemove(p_150642_, p_150643_, p_150644_)
}
override fun onTake(p_150645_: Player, p_150646_: ItemStack) {
if (p_150646_.isNotEmpty) {
entangling.removeItem(0, 1)
entangling.removeItem(1, 1)
}
super.onTake(p_150645_, p_150646_)
}
}
override fun removed(p_38940_: Player) {
super.removed(p_38940_)
clearContainer(p_38940_, entangling)
}
init {
addSlot(outputs)
addSlot(entanglingA)
addSlot(entanglingB)
addSlot(entanglingC)
mapQuickMoveToInventory(entanglingA)
mapQuickMoveToInventory(entanglingB)
mapQuickMoveToInventory(entanglingC)
outputs.forEach(::mapQuickMoveToInventory)
addStorageSlot(inputs)
addInventorySlots()
}
}

View File

@ -141,7 +141,7 @@ class MatterPanelMenu(
}
val sorting: ItemSorter by mSynchronizer.ComputedField(
getter = { tile?.getPlayerSettings(ply)?.sorter ?: ItemSorter.DEFAULT },
getter = { tile?.getPlayerSettings(player)?.sorter ?: ItemSorter.DEFAULT },
codec = ItemSorter::class.codec(),
observer = {
patterns.sortWith(actualComparator)
@ -151,7 +151,7 @@ class MatterPanelMenu(
})
val isAscending: Boolean by mSynchronizer.ComputedField(
getter = { tile?.getPlayerSettings(ply)?.ascending ?: true },
getter = { tile?.getPlayerSettings(player)?.ascending ?: true },
codec = BooleanValueCodec,
observer = {
patterns.sortWith(actualComparator)
@ -184,8 +184,8 @@ class MatterPanelMenu(
}
}
val changeIsAscending = booleanInput(allowSpectators = true) { tile?.getPlayerSettings(ply)?.ascending = it }
val changeSorting = PlayerInput(ItemSorter::class.codec(), allowSpectators = true) { tile?.getPlayerSettings(ply)?.sorter = it }
val changeIsAscending = booleanInput(allowSpectators = true) { tile?.getPlayerSettings(player)?.ascending = it }
val changeSorting = PlayerInput(ItemSorter::class.codec(), allowSpectators = true) { tile?.getPlayerSettings(player)?.sorter = it }
val sortingGS = GetterSetter.of(::sorting, changeSorting::accept)
val isAscendingGS = GetterSetter.of(::isAscending, changeIsAscending::accept)

View File

@ -10,7 +10,7 @@ import net.minecraft.world.SimpleContainer
import ru.dbotthepony.mc.otm.container.CombinedContainer
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
import ru.dbotthepony.mc.otm.menu.OutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput
@ -25,7 +25,7 @@ class MatterReplicatorMenu @JvmOverloads constructor(
) : MatteryPoweredMenu(MMenus.MATTER_REPLICATOR, p_38852_, inventory, tile) {
val matter = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter))
val progress = ProgressGaugeWidget(this, tile)
val storageSlots: List<MachineOutputSlot>
val storageSlots: List<OutputSlot>
val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig)
val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget)
@ -36,7 +36,7 @@ class MatterReplicatorMenu @JvmOverloads constructor(
val container = CombinedContainer(tile?.outputContainer ?: SimpleContainer(3), tile?.dustContainer ?: SimpleContainer(2))
storageSlots = immutableList(5) {
addStorageSlot(MachineOutputSlot(container, it, onTake = {
addStorageSlot(OutputSlot(container, it, onTake = {
if (inventory.player is ServerPlayer && it.isNotEmpty)
TakeItemOutOfReplicatorTrigger.trigger(inventory.player as ServerPlayer, it) }))
}

View File

@ -48,8 +48,8 @@ class DriveViewerMenu(
val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
val settings = object : DriveViewerBlockEntity.ISettings {
override var sorting: ItemStorageStackSorter by EnumInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(ply)::sorting }).also { it.addListener { changes() } }
override var isAscending: Boolean by BooleanInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(ply)::isAscending }).also { it.addListener { changes() } }
override var sorting: ItemStorageStackSorter by EnumInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(player)::sorting }).also { it.addListener { changes() } }
override var isAscending: Boolean by BooleanInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(player)::isAscending }).also { it.addListener { changes() } }
private fun changes() {
if (isAscending) {

View File

@ -86,11 +86,11 @@ class ItemMonitorMenu(
override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null)
val settings = object : IItemMonitorPlayerSettings {
override var ingredientPriority by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::ingredientPriority })
override var resultTarget by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::resultTarget })
override var craftingAmount by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::craftingAmount })
override var sorting by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::sorting }).also { it.addListener { changes() } }
override var ascendingSort by BooleanInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::ascendingSort }).also { it.addListener { changes() } }
override var ingredientPriority by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::ingredientPriority })
override var resultTarget by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::resultTarget })
override var craftingAmount by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::craftingAmount })
override var sorting by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::sorting }).also { it.addListener { changes() } }
override var ascendingSort by BooleanInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::ascendingSort }).also { it.addListener { changes() } }
private fun changes() {
if (ascendingSort) {
@ -140,7 +140,7 @@ class ItemMonitorMenu(
}
if (!simulate && remaining.isNotEmpty) {
ply.spawnAtLocation(remaining)
player.spawnAtLocation(remaining)
}
if (!simulate) {

View File

@ -24,8 +24,8 @@ class AndroidStationMenu @JvmOverloads constructor(
tile: AndroidStationBlockEntity? = null
) : MatteryPoweredMenu(MMenus.ANDROID_STATION, containerID, inventory, tile) {
private fun container(target: (MatteryPlayerCapability) -> KMutableProperty0<ItemStack>): Container {
if (ply is ServerPlayer)
return PartContainer(target.invoke(ply.matteryPlayer ?: throw NullPointerException("OTM player capability is missing")))
if (player is ServerPlayer)
return PartContainer(target.invoke(player.matteryPlayer ?: throw NullPointerException("OTM player capability is missing")))
else
return SimpleContainer(1)
}

View File

@ -5,7 +5,7 @@ import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting
import ru.dbotthepony.mc.otm.block.entity.tech.CobblerBlockEntity
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
import ru.dbotthepony.mc.otm.menu.OutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput
@ -17,7 +17,7 @@ class CobblerMenu @JvmOverloads constructor(
inventory: Inventory,
tile: CobblerBlockEntity? = null
) : MatteryMenu(MMenus.COBBLESTONE_GENERATOR, p_38852_, inventory, tile) {
val storageSlots = (tile?.container ?: SimpleContainer(CobblerBlockEntity.CONTAINER_SIZE)).let { c -> immutableList(c.containerSize) { addStorageSlot(MachineOutputSlot(c, it)) } }
val storageSlots = (tile?.container ?: SimpleContainer(CobblerBlockEntity.CONTAINER_SIZE)).let { c -> immutableList(c.containerSize) { addStorageSlot(OutputSlot(c, it)) } }
val redstone = EnumInputWithFeedback<RedstoneSetting>(this)
val itemConfig = ItemConfigPlayerInput(this)

View File

@ -40,7 +40,7 @@ class EssenceStorageMenu @JvmOverloads constructor(
val storeLevels = intInput {
if (it > 0) {
val ply = ply as ServerPlayer
val ply = player as ServerPlayer
tile!!
if (it == 1) {
@ -63,7 +63,7 @@ class EssenceStorageMenu @JvmOverloads constructor(
val dispenseLevels = intInput {
if (it > 0) {
val ply = ply as ServerPlayer
val ply = player as ServerPlayer
tile!!
if (it == 1) {

View File

@ -4,7 +4,7 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
import ru.dbotthepony.mc.otm.menu.OutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
@ -19,7 +19,7 @@ class PlatePressMenu @JvmOverloads constructor(
tile: PlatePressBlockEntity? = null
) : MatteryPoweredMenu(MMenus.PLATE_PRESS, containerID, inventory, tile) {
val inputSlot = MatterySlot(tile?.inputContainer ?: SimpleContainer(1), 0)
val outputSlot = MachineOutputSlot(tile?.outputContainer ?: SimpleContainer(1), 0) { tile?.popExperience(ply as ServerPlayer) }
val outputSlot = OutputSlot(tile?.outputContainer ?: SimpleContainer(1), 0) { tile?.popExperience(player as ServerPlayer) }
val progressGauge = ProgressGaugeWidget(this, tile)
val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true)

View File

@ -4,7 +4,7 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.block.entity.tech.PoweredFurnaceBlockEntity
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
import ru.dbotthepony.mc.otm.menu.OutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
@ -21,7 +21,7 @@ class PoweredFurnaceMenu(
tile: PoweredFurnaceBlockEntity? = null
) : MatteryPoweredMenu(MMenus.POWERED_FURNACE, containerID, inventory, tile) {
val inputSlots = makeSlots(tile?.inputs, 2, ::MatterySlot)
val outputSlots = makeSlots(tile?.outputs, 2) { c, s -> MachineOutputSlot(c, s) { tile?.popExperience(ply as ServerPlayer) } }
val outputSlots = makeSlots(tile?.outputs, 2) { c, s -> OutputSlot(c, s) { tile?.popExperience(player as ServerPlayer) } }
val progressGauge = immutableList(2) { ProgressGaugeWidget(this, tile?.jobEventLoops?.get(it)) }
val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true)

View File

@ -4,7 +4,7 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity
import ru.dbotthepony.mc.otm.menu.MachineOutputSlot
import ru.dbotthepony.mc.otm.menu.OutputSlot
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
@ -21,7 +21,7 @@ class TwinPlatePressMenu @JvmOverloads constructor(
tile: PlatePressBlockEntity? = null
) : MatteryPoweredMenu(MMenus.TWIN_PLATE_PRESS, containerID, inventory, tile) {
val inputSlots = makeSlots(tile?.inputContainer ?: SimpleContainer(2), ::MatterySlot)
val outputSlots = makeSlots(tile?.outputContainer ?: SimpleContainer(2)) { a, b -> MachineOutputSlot(a, b) { tile?.popExperience(ply as ServerPlayer) } }
val outputSlots = makeSlots(tile?.outputContainer ?: SimpleContainer(2)) { a, b -> OutputSlot(a, b) { tile?.popExperience(player as ServerPlayer) } }
val progressGauge0 = ProgressGaugeWidget(this, tile?.jobEventLoops?.get(0))
val progressGauge1 = ProgressGaugeWidget(this, tile?.jobEventLoops?.get(1))

View File

@ -0,0 +1,41 @@
package ru.dbotthepony.mc.otm.recipe
import net.minecraft.core.NonNullList
import net.minecraft.world.Container
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.crafting.Recipe
import net.minecraft.world.item.crafting.RecipeSerializer
import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
// overrides all methods to fix Kotlin bug related to implementation delegation (to allow easy optics)
// https://youtrack.jetbrains.com/issue/KT-55080/Change-the-behavior-of-inheritance-delegation-to-delegates-implementing-Java-interfaces-with-default-methods
interface IMatteryRecipe<C : Container> : Recipe<C> {
override fun getRemainingItems(p_44004_: C): NonNullList<ItemStack> {
return super.getRemainingItems(p_44004_)
}
override fun getIngredients(): NonNullList<Ingredient> {
return super.getIngredients()
}
override fun isSpecial(): Boolean {
return super.isSpecial()
}
override fun showNotification(): Boolean {
return super.showNotification()
}
override fun getGroup(): String {
return super.getGroup()
}
override fun getToastSymbol(): ItemStack {
return super.getToastSymbol()
}
override fun isIncomplete(): Boolean {
return super.isIncomplete()
}
}

View File

@ -0,0 +1,151 @@
package ru.dbotthepony.mc.otm.recipe
import net.minecraft.core.NonNullList
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.crafting.Ingredient
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.collect.allEqual
import ru.dbotthepony.mc.otm.core.collect.any
import ru.dbotthepony.mc.otm.core.collect.flatMap
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.isActuallyEmpty
import ru.dbotthepony.mc.otm.core.isNotEmpty
import java.util.function.Predicate
interface IIngredientMatrix : Predicate<CraftingContainer>, Iterable<Ingredient> {
val width: Int
val height: Int
val isEmpty: Boolean
get() = width == 0 || height == 0
val isIncomplete: Boolean
get() = iterator().any { it.isActuallyEmpty }
operator fun get(column: Int, row: Int): Ingredient
operator fun get(column: Int, row: Int, flop: Boolean): Ingredient {
return if (flop)
get(width - column - 1, row)
else
get(column, row)
}
override fun iterator(): Iterator<Ingredient> {
return (0 until width).iterator().flatMap { x ->
(0 until height).iterator().map { y ->
get(x, y)
}
}
}
val ingredients: NonNullList<Ingredient> get() {
val result = NonNullList.createWithCapacity<Ingredient>(width * height)
for (x in 0 until width) {
for (y in 0 until height) {
result.add(get(x, y))
}
}
return result
}
fun test(t: CraftingContainer, fromColumn: Int, fromRow: Int, flop: Boolean): Boolean {
if (t.width - fromColumn < width || t.height - fromRow < height)
return false
for (column in 0 until width)
for (row in 0 until height)
if (!this[column, row, flop].test(t[fromColumn + column, fromRow + row, flop]))
return false
return true
}
fun preemptiveTest(t: CraftingContainer, fromColumn: Int, fromRow: Int, flop: Boolean): Boolean {
if (t.width - fromColumn < width || t.height - fromRow < height)
return false
for (column in 0 until width) {
for (row in 0 until height) {
val item = t[fromColumn + column, fromRow + row, flop]
val ingredient = this[column, row, flop]
if (!ingredient.test(item) && item.isNotEmpty) {
return false
}
}
}
return true
}
override fun test(t: CraftingContainer): Boolean {
if (t.width < width || t.height < height)
return false
for (column in 0 .. t.width - width)
for (row in 0 .. t.height - height)
if (test(t, column, row, false) || test(t, column, row, true))
return true
return false
}
fun preemptiveTest(t: CraftingContainer): Boolean {
if (t.width < width || t.height < height)
return false
for (column in 0 .. t.width - width)
for (row in 0 .. t.height - height)
if (preemptiveTest(t, column, row, false) || preemptiveTest(t, column, row, true))
return true
return false
}
companion object : IIngredientMatrix {
override val width: Int
get() = 0
override val height: Int
get() = 0
override fun get(column: Int, row: Int): Ingredient {
return Ingredient.EMPTY
}
}
}
class IngredientMatrix(override val width: Int, override val height: Int) : IIngredientMatrix {
private val data = Array(width * height) { Ingredient.EMPTY }
override fun get(column: Int, row: Int): Ingredient {
require(column in 0 until width) { "Column out of bounds: $column (matrix width: $width)" }
require(row in 0 until height) { "Row out of bounds: $row > (matrix height: $height)" }
return data[column + row * width]
}
operator fun set(column: Int, row: Int, value: Ingredient) {
require(column in 0 until width) { "Column out of bounds: $column (matrix width: $width)" }
require(row in 0 until height) { "Row out of bounds: $row > (matrix height: $height)" }
data[column + row * width] = value
}
companion object {
fun of(vararg values: Collection<Ingredient>): IngredientMatrix {
if (!values.iterator().map { it.size }.allEqual()) {
throw IllegalArgumentException("One or more rows have different number of columns than the rest")
}
val result = IngredientMatrix(values.first().size, values.size)
for ((y, l) in values.withIndex()) {
for ((x, i) in l.withIndex()) {
result[x, y] = i
}
}
return result
}
}
}

View File

@ -0,0 +1,170 @@
package ru.dbotthepony.mc.otm.recipe
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.NonNullList
import net.minecraft.core.RegistryAccess
import net.minecraft.core.UUIDUtil
import net.minecraft.data.recipes.FinishedRecipe
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.crafting.Recipe
import net.minecraft.world.item.crafting.RecipeSerializer
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.capability.matter.matter
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.container.iterator
import ru.dbotthepony.mc.otm.core.collect.filterNotNull
import ru.dbotthepony.mc.otm.core.collect.forEach
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.tagNotNull
import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
import ru.dbotthepony.mc.otm.data.DecimalCodec
import ru.dbotthepony.mc.otm.data.IngredientMatrixCodec
import ru.dbotthepony.mc.otm.data.minRange
import ru.dbotthepony.mc.otm.registry.MRecipes
import java.util.Optional
import java.util.UUID
import kotlin.jvm.optionals.getOrElse
interface IMatterEntanglerRecipe : IMatteryRecipe<CraftingContainer> {
val matter: Decimal
val ticks: Double
val ingredients: IIngredientMatrix
val result: ItemStack
fun preemptivelyMatches(container: CraftingContainer, level: Level): Boolean
}
open class MatterEntanglerRecipe(
private val id: ResourceLocation,
override val ingredients: IIngredientMatrix,
override val matter: Decimal,
override val ticks: Double,
override val result: ItemStack,
val uuidKey: String = "uuid",
val fixedUuid: Optional<UUID> = Optional.empty()
) : IMatterEntanglerRecipe {
override fun matches(container: CraftingContainer, level: Level): Boolean {
if (isIncomplete) return false
return ingredients.test(container)
}
override fun preemptivelyMatches(container: CraftingContainer, level: Level): Boolean {
if (isIncomplete) return false
return ingredients.preemptiveTest(container)
}
override fun assemble(container: CraftingContainer, registry: RegistryAccess): ItemStack {
return result.copy().also {
it.tagNotNull[uuidKey] = fixedUuid.getOrElse { UUID.randomUUID() }
}
}
override fun canCraftInDimensions(width: Int, height: Int): Boolean {
return width >= ingredients.width && height >= ingredients.height
}
override fun getResultItem(registry: RegistryAccess): ItemStack {
return result
}
override fun getId(): ResourceLocation {
return id
}
override fun getSerializer(): RecipeSerializer<*> {
return SERIALIZER
}
override fun getType(): RecipeType<*> {
return MRecipes.MATTER_ENTANGLER
}
override fun getIngredients(): NonNullList<Ingredient> {
return ingredients.ingredients
}
override fun isIncomplete(): Boolean {
return result.isEmpty || ingredients.isIncomplete
}
override fun isSpecial(): Boolean {
return true
}
fun toFinished(): FinishedRecipe {
return SERIALIZER.toFinished(this)
}
fun energetic() = Energy(this)
fun matter() = Matter(this)
open class Energy(val parent: MatterEntanglerRecipe) : IMatterEntanglerRecipe by parent {
override fun assemble(container: CraftingContainer, registry: RegistryAccess): ItemStack {
return parent.assemble(container, registry).also { result ->
container.iterator().map { it.matteryEnergy }.filterNotNull().forEach {
result.matteryEnergy!!.batteryLevel += it.batteryLevel
}
}
}
fun toFinished(): FinishedRecipe {
return ENERGY_SERIALIZER.toFinished(this)
}
override fun getSerializer(): RecipeSerializer<*> {
return ENERGY_SERIALIZER
}
}
open class Matter(val parent: MatterEntanglerRecipe) : IMatterEntanglerRecipe by parent {
override fun assemble(container: CraftingContainer, registry: RegistryAccess): ItemStack {
return parent.assemble(container, registry).also { result ->
container.iterator().map { it.matter }.filterNotNull().forEach {
result.matter!!.storedMatter += it.storedMatter
}
}
}
fun toFinished(): FinishedRecipe {
return MATTER_SERIALIZER.toFinished(this)
}
override fun getSerializer(): RecipeSerializer<*> {
return MATTER_SERIALIZER
}
}
companion object {
val SERIALIZER = Codec2RecipeSerializer<MatterEntanglerRecipe>(
MatterEntanglerRecipe(
ResourceLocation(OverdriveThatMatters.MOD_ID, "null"),
IIngredientMatrix.Companion,
Decimal.ZERO,
0.0,
ItemStack.EMPTY,
)
) { context ->
RecordCodecBuilder.create {
it.group(
IngredientMatrixCodec.fieldOf("ingredients").forGetter(MatterEntanglerRecipe::ingredients),
DecimalCodec.minRange(Decimal.ZERO).fieldOf("matter").forGetter(MatterEntanglerRecipe::matter),
Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(MatterEntanglerRecipe::ticks),
ItemStack.CODEC.fieldOf("result").forGetter(MatterEntanglerRecipe::result),
Codec.STRING.optionalFieldOf("uuidKey", "uuid").forGetter(MatterEntanglerRecipe::uuidKey),
UUIDUtil.STRING_CODEC.optionalFieldOf("fixedUuid").forGetter(MatterEntanglerRecipe::fixedUuid)
).apply(it) { a, b, c, d, e, f -> MatterEntanglerRecipe(context.id, a, b, c, d, e, f) }
}
}
val ENERGY_SERIALIZER = SERIALIZER.xmap(::Energy, Energy::parent)
val MATTER_SERIALIZER = SERIALIZER.xmap(::Matter, Matter::parent)
}
}

View File

@ -125,7 +125,7 @@ class PainterRecipe(
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),
).apply(it) { a, b, c -> PainterRecipe(context.invoke(), a, b, c) }
).apply(it) { a, b, c -> PainterRecipe(context.id, a, b, c) }
}
}
}

View File

@ -89,7 +89,7 @@ class PlatePressRecipe(
Codec.INT.minRange(1).optionalFieldOf("count", 1).forGetter(PlatePressRecipe::count),
Codec.INT.minRange(0).optionalFieldOf("workTime", 200).forGetter(PlatePressRecipe::workTime),
FloatProvider.CODEC.optionalFieldOf("experience", ConstantFloat.ZERO).forGetter(PlatePressRecipe::experience)
).apply(it) { a, b, c, d, e -> PlatePressRecipe(context.invoke(), a, b, c, d, e) }
).apply(it) { a, b, c, d, e -> PlatePressRecipe(context.id, a, b, c, d, e) }
}
}
}

View File

@ -72,6 +72,7 @@ object MBlockEntities {
val INFINITE_WATER_SOURCE by register(MNames.INFINITE_WATER_SOURCE, ::InfiniteWaterSourceBlockEntity, MBlocks::INFINITE_WATER_SOURCE)
val DEV_CHEST by register(MNames.DEV_CHEST, ::DevChestBlockEntity, MBlocks::DEV_CHEST)
val PAINTER by register(MNames.PAINTER, ::PainterBlockEntity, MBlocks::PAINTER)
val MATTER_ENTANGLER by register(MNames.MATTER_ENTANGLER, ::MatterEntanglerBlockEntity, MBlocks::MATTER_ENTANGLER)
val POWERED_FURNACE: BlockEntityType<PoweredFurnaceBlockEntity> by registry.register(MNames.POWERED_FURNACE) { BlockEntityType.Builder.of({ a, b -> MBlocks.POWERED_FURNACE.newBlockEntity(a, b) }, MBlocks.POWERED_FURNACE).build(null) }
val POWERED_BLAST_FURNACE: BlockEntityType<PoweredFurnaceBlockEntity> by registry.register(MNames.POWERED_BLAST_FURNACE) { BlockEntityType.Builder.of({ a, b -> MBlocks.POWERED_BLAST_FURNACE.newBlockEntity(a, b) }, MBlocks.POWERED_BLAST_FURNACE).build(null) }

View File

@ -64,6 +64,7 @@ import ru.dbotthepony.mc.otm.block.tech.AndroidChargerBlock
import ru.dbotthepony.mc.otm.block.tech.CobblerBlock
import ru.dbotthepony.mc.otm.block.tech.EssenceStorageBlock
import ru.dbotthepony.mc.otm.block.decorative.PainterBlock
import ru.dbotthepony.mc.otm.block.matter.MatterEntanglerBlock
import ru.dbotthepony.mc.otm.block.tech.PoweredFurnaceBlock
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.TranslatableComponent
@ -102,6 +103,7 @@ object MBlocks {
val ESSENCE_STORAGE: EssenceStorageBlock by registry.register(MNames.ESSENCE_STORAGE) { EssenceStorageBlock() }
val MATTER_RECONSTRUCTOR: MatterReconstructorBlock by registry.register(MNames.MATTER_RECONSTRUCTOR) { MatterReconstructorBlock() }
val PAINTER: PainterBlock by registry.register(MNames.PAINTER) { PainterBlock() }
val MATTER_ENTANGLER: MatterEntanglerBlock by registry.register(MNames.MATTER_ENTANGLER) { MatterEntanglerBlock() }
val STORAGE_BUS: Block by registry.register(MNames.STORAGE_BUS) { StorageBusBlock() }
val STORAGE_IMPORTER: Block by registry.register(MNames.STORAGE_IMPORTER) { StorageImporterBlock() }

View File

@ -152,10 +152,11 @@ object MItems {
}
val PAINTER: BlockItem by registry.register(MNames.PAINTER) { BlockItem(MBlocks.PAINTER, DEFAULT_PROPERTIES) }
val MATTER_ENTANGLER: BlockItem by registry.register(MNames.MATTER_ENTANGLER) { BlockItem(MBlocks.MATTER_ENTANGLER, DEFAULT_PROPERTIES) }
val MACHINES = SupplierList(
::ANDROID_STATION, ::ANDROID_CHARGER, ::BATTERY_BANK, ::MATTER_DECOMPOSER, ::MATTER_CAPACITOR_BANK, ::MATTER_CABLE, ::PATTERN_STORAGE,
::MATTER_SCANNER, ::MATTER_PANEL, ::MATTER_REPLICATOR, ::MATTER_BOTTLER, ::ENERGY_COUNTER, ::CHEMICAL_GENERATOR,
::MATTER_SCANNER, ::MATTER_PANEL, ::MATTER_REPLICATOR, ::MATTER_BOTTLER, ::MATTER_ENTANGLER, ::ENERGY_COUNTER, ::CHEMICAL_GENERATOR,
::MATTER_RECYCLER, ::PLATE_PRESS, ::TWIN_PLATE_PRESS, ::POWERED_FURNACE, ::POWERED_BLAST_FURNACE,
::POWERED_SMOKER,
::STORAGE_BUS, ::STORAGE_IMPORTER, ::STORAGE_EXPORTER, ::DRIVE_VIEWER,

View File

@ -17,6 +17,7 @@ import ru.dbotthepony.mc.otm.client.screen.matter.MatterReconstructorScreen
import ru.dbotthepony.mc.otm.client.screen.matter.MatterBottlerScreen
import ru.dbotthepony.mc.otm.client.screen.matter.MatterCapacitorBankScreen
import ru.dbotthepony.mc.otm.client.screen.matter.MatterDecomposerScreen
import ru.dbotthepony.mc.otm.client.screen.matter.MatterEntanglerScreen
import ru.dbotthepony.mc.otm.client.screen.matter.MatterPanelScreen
import ru.dbotthepony.mc.otm.client.screen.matter.MatterRecyclerScreen
import ru.dbotthepony.mc.otm.client.screen.matter.MatterReplicatorScreen
@ -36,7 +37,7 @@ import ru.dbotthepony.mc.otm.client.screen.tech.CobblerScreen
import ru.dbotthepony.mc.otm.client.screen.tech.EnergyCounterScreen
import ru.dbotthepony.mc.otm.client.screen.tech.EnergyServoScreen
import ru.dbotthepony.mc.otm.client.screen.tech.EssenceStorageScreen
import ru.dbotthepony.mc.otm.client.screen.tech.PainterScreen
import ru.dbotthepony.mc.otm.client.screen.decorative.PainterScreen
import ru.dbotthepony.mc.otm.client.screen.tech.PlatePressScreen
import ru.dbotthepony.mc.otm.client.screen.tech.PoweredFurnaceScreen
import ru.dbotthepony.mc.otm.client.screen.tech.TwinPlatePressScreen
@ -48,6 +49,7 @@ import ru.dbotthepony.mc.otm.menu.matter.MatterReconstructorMenu
import ru.dbotthepony.mc.otm.menu.matter.MatterBottlerMenu
import ru.dbotthepony.mc.otm.menu.matter.MatterCapacitorBankMenu
import ru.dbotthepony.mc.otm.menu.matter.MatterDecomposerMenu
import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu
import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu
import ru.dbotthepony.mc.otm.menu.matter.MatterRecyclerMenu
import ru.dbotthepony.mc.otm.menu.matter.MatterReplicatorMenu
@ -67,7 +69,7 @@ import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu
import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu
import ru.dbotthepony.mc.otm.menu.tech.EnergyServoMenu
import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu
import ru.dbotthepony.mc.otm.menu.tech.PainterMenu
import ru.dbotthepony.mc.otm.menu.decorative.PainterMenu
import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu
import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu
import ru.dbotthepony.mc.otm.menu.tech.TwinPlatePressMenu
@ -103,6 +105,7 @@ object MMenus {
val ITEM_REPAIER: MenuType<MatterReconstructorMenu> by registry.register(MNames.MATTER_RECONSTRUCTOR) { MenuType(::MatterReconstructorMenu, FeatureFlags.VANILLA_SET) }
val FLUID_TANK: MenuType<FluidTankMenu> by registry.register(MNames.FLUID_TANK) { MenuType(::FluidTankMenu, FeatureFlags.VANILLA_SET) }
val PAINTER: MenuType<PainterMenu> by registry.register(MNames.PAINTER) { MenuType(::PainterMenu, FeatureFlags.VANILLA_SET) }
val MATTER_ENTANGLER: MenuType<MatterEntanglerMenu> by registry.register(MNames.MATTER_ENTANGLER) { MenuType(::MatterEntanglerMenu, FeatureFlags.VANILLA_SET) }
val STORAGE_BUS: MenuType<StorageBusMenu> by registry.register(MNames.STORAGE_BUS) { MenuType(::StorageBusMenu, FeatureFlags.VANILLA_SET) }
val STORAGE_IMPORTER_EXPORTER: MenuType<StorageImporterExporterMenu> by registry.register(MNames.STORAGE_IMPORTER) { MenuType(::StorageImporterExporterMenu, FeatureFlags.VANILLA_SET) }
@ -146,6 +149,7 @@ object MMenus {
MenuScreens.register(FLUID_TANK, ::FluidTankScreen)
MenuScreens.register(POWERED_FURNACE, ::PoweredFurnaceScreen)
MenuScreens.register(PAINTER, ::PainterScreen)
MenuScreens.register(MATTER_ENTANGLER, ::MatterEntanglerScreen)
}
}
}

View File

@ -16,6 +16,7 @@ object MNames {
const val INFINITE_WATER_SOURCE = "infinite_water_source"
const val DEV_CHEST = "dev_chest"
const val PAINTER = "painter"
const val MATTER_ENTANGLER = "matter_entangler"
// blocks
const val ANDROID_STATION = "android_station"

View File

@ -10,6 +10,8 @@ import net.minecraftforge.registries.RegistryObject
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.recipe.EnergyContainerRecipe
import ru.dbotthepony.mc.otm.recipe.ExplosiveHammerPrimingRecipe
import ru.dbotthepony.mc.otm.recipe.IMatterEntanglerRecipe
import ru.dbotthepony.mc.otm.recipe.MatterEntanglerRecipe
import ru.dbotthepony.mc.otm.recipe.PainterRecipe
import ru.dbotthepony.mc.otm.recipe.PlatePressRecipe
import ru.dbotthepony.mc.otm.recipe.UpgradeRecipe
@ -38,6 +40,7 @@ object MRecipes {
val PLATE_PRESS by register<PlatePressRecipe>("plate_press")
val PAINTER by register<PainterRecipe>("painter")
val MATTER_ENTANGLER by register<IMatterEntanglerRecipe>("matter_entangler")
init {
serializers.register("plate_press") { PlatePressRecipe.SERIALIZER }
@ -45,5 +48,8 @@ object MRecipes {
serializers.register("upgrade") { UpgradeRecipe.Companion }
serializers.register("hammer_priming") { ExplosiveHammerPrimingRecipe.Companion }
serializers.register("painter") { PainterRecipe.SERIALIZER }
serializers.register("matter_entangler") { MatterEntanglerRecipe.SERIALIZER }
serializers.register("matter_entangler_energetic") { MatterEntanglerRecipe.ENERGY_SERIALIZER }
serializers.register("matter_entangler_matter") { MatterEntanglerRecipe.MATTER_SERIALIZER }
}
}