diff --git a/gradle.properties b/gradle.properties index face01890..ef09c8011 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,7 +22,7 @@ mixin_version=0.8.5 neogradle.subsystems.parchment.minecraftVersion=1.21.1 neogradle.subsystems.parchment.mappingsVersion=2024.11.17 -kommons_version=3.5.2 +kommons_version=3.6.0 caffeine_cache_version=3.1.5 jei_version=19.16.4.171 diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index 67ead1708..a74100d73 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -631,6 +631,8 @@ private fun blocks(provider: MatteryLanguageProvider) { add(MBlocks.ENERGY_COUNTER[null]!!, "switch", "Switch input facing") add(MBlocks.ENERGY_COUNTER[null]!!, "limit", "I/O Limit. -1 means no limit") add(MBlocks.ENERGY_COUNTER[null]!!, "display_this", "Display this information on block's screen") + add(MBlocks.ENERGY_COUNTER[null]!!, "do_pull", "Pull energy from input side and push to output") + add(MBlocks.ENERGY_COUNTER[null]!!, "dont_pull", "Don't pull energy") addBlock(MBlocks.CHEMICAL_GENERATOR.values, "Chemical Generator") addBlock(MBlocks.CHEMICAL_GENERATOR.values, "desc", "Generates power by burning solid fuels") @@ -987,6 +989,18 @@ private fun androidFeatures(provider: MatteryLanguageProvider) { private fun gui(provider: MatteryLanguageProvider) { with(provider.english) { + gui("quickmove_from.restock", "Restock from storage") + gui("quickmove_from.restock_with_move", "Quickstack from storage") + gui("quickmove_from.move", "Take all") + + gui("quickmove_to.restock", "Restock to storage") + gui("quickmove_to.restock_with_move", "Quickstack to storage") + gui("quickmove_to.move", "Deposit all") + gui("quickmove.exchange", "Smart exchange with storage") + gui("quickmove.exchange.desc", "Filtered slots get restocked, everything else gets quickstacked from Exopack") + + gui("quickmove_hint", "Right click to show all variants") + gui("exopack.accept_wireless_charge", "Accept wireless charging") gui("exopack.dont_accept_wireless_charge", "Do not accept wireless charging") diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt index cc3b3a7d4..661cc85fb 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt @@ -635,6 +635,8 @@ private fun blocks(provider: MatteryLanguageProvider) { add(MBlocks.ENERGY_COUNTER[null]!!, "switch", "Сменить сторону входа") add(MBlocks.ENERGY_COUNTER[null]!!, "limit", "Лимит ввода/вывода. -1 для отключения лимитов") add(MBlocks.ENERGY_COUNTER[null]!!, "display_this", "Отображать эти данные на экране блока") + add(MBlocks.ENERGY_COUNTER[null]!!, "do_pull", "Забирать энергию на входе и выталкивать её на выходе") + add(MBlocks.ENERGY_COUNTER[null]!!, "dont_pull", "Не забирать энергию на входе") addBlock(MBlocks.CHEMICAL_GENERATOR.values, "Химический генератор") addBlock(MBlocks.CHEMICAL_GENERATOR.values, "desc", "Генерирует энергию сжигая твёрдое топливо") @@ -980,6 +982,18 @@ private fun androidFeatures(provider: MatteryLanguageProvider) { private fun gui(provider: MatteryLanguageProvider) { with(provider.russian) { + gui("quickmove_from.restock", "Быстрое пополнение из хранилища") + gui("quickmove_from.restock_with_move", "Быстрое складирование из хранилища") + gui("quickmove_from.move", "Взять всё") + + gui("quickmove_to.restock", "Быстрое пополнение в хранилище") + gui("quickmove_to.restock_with_move", "Быстрое складирование в хранилище") + gui("quickmove_to.move", "Переместить всё") + gui("quickmove.exchange", "Умный обмен с хранилищем") + gui("quickmove.exchange.desc", "Слоты с фильтрами заполняются до максимума, всё остальное быстро складируется из экзопака") + + gui("quickmove_hint", "Правый клик чтоб увидеть все варианты") + gui("exopack.accept_wireless_charge", "Принимать заряд от беспроводных зарядников") gui("exopack.dont_accept_wireless_charge", "Не принимать заряд от беспроводных зарядников") diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java index 6f30afc7e..77b2c9f5e 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java @@ -15,7 +15,6 @@ import ru.dbotthepony.mc.otm.capability.matter.IReplicationTaskProvider; import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage; import ru.dbotthepony.mc.otm.graph.matter.MatterNode; import ru.dbotthepony.mc.otm.graph.storage.StorageNode; -import ru.dbotthepony.mc.otm.storage.StorageStack; import javax.annotation.Nonnull; @@ -70,4 +69,8 @@ public class MatteryCapability { @Nonnull @NotNull public static final ItemCapability UPGRADE = ItemCapability.createVoid(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "machine_upgrade"), IMatteryUpgrade.class); + + @Nonnull + @NotNull + public static final BlockCapability QUICK_STACK_CONTAINER = BlockCapability.createVoid(ResourceLocation.fromNamespaceAndPath(OverdriveThatMatters.MOD_ID, "quick_stack_container"), IQuickStackContainer.class); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/mixin/ShulkerBoxBlockEntityMixin.java b/src/main/java/ru/dbotthepony/mc/otm/mixin/ShulkerBoxBlockEntityMixin.java new file mode 100644 index 000000000..5e44c483b --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/mixin/ShulkerBoxBlockEntityMixin.java @@ -0,0 +1,16 @@ +package ru.dbotthepony.mc.otm.mixin; + +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import ru.dbotthepony.mc.otm.compat.vanilla.MatteryShulkerBoxMenu; + +@Mixin(ShulkerBoxBlockEntity.class) +public abstract class ShulkerBoxBlockEntityMixin { + @Overwrite(remap = false) + public AbstractContainerMenu createMenu(int p_59312_, Inventory p_59313_) { + return new MatteryShulkerBoxMenu(p_59312_, p_59313_, (ShulkerBoxBlockEntity) (Object) this); + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt index 61dbff0cf..505b08e43 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/OverdriveThatMatters.kt @@ -12,6 +12,8 @@ import ru.dbotthepony.mc.otm.player.android.AndroidResearchResults import ru.dbotthepony.mc.otm.player.android.feature.EnderTeleporterFeature import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity import ru.dbotthepony.mc.otm.block.entity.decorative.DevChestBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.AbstractPoweredFurnaceBlockEntity +import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity import ru.dbotthepony.mc.otm.player.MatteryPlayer import ru.dbotthepony.mc.otm.capability.drive.DrivePool import ru.dbotthepony.mc.otm.client.AndroidAbilityKeyMapping @@ -41,6 +43,7 @@ import ru.dbotthepony.mc.otm.client.render.blockentity.MatterBatteryBankRenderer import ru.dbotthepony.mc.otm.compat.curios.isCuriosLoaded import ru.dbotthepony.mc.otm.compat.curios.onCuriosSlotModifiersUpdated import ru.dbotthepony.mc.otm.compat.vanilla.MatteryChestMenu +import ru.dbotthepony.mc.otm.compat.vanilla.VanillaMenuTypes import ru.dbotthepony.mc.otm.config.PlayerConfig import ru.dbotthepony.mc.otm.config.CablesConfig import ru.dbotthepony.mc.otm.config.ClientConfig @@ -49,6 +52,7 @@ import ru.dbotthepony.mc.otm.config.ItemsConfig import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.config.ServerConfig import ru.dbotthepony.mc.otm.config.ToolsConfig +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.data.FlywheelMaterials import ru.dbotthepony.mc.otm.data.world.BooleanProvider import ru.dbotthepony.mc.otm.data.world.DecimalProvider @@ -131,7 +135,7 @@ object OverdriveThatMatters { MLootNumberProviders.register(MOD_BUS) StorageStack.register(MOD_BUS) - MatteryChestMenu.register(MOD_BUS) + VanillaMenuTypes.register(MOD_BUS) MCreativeTabs.initialize(MOD_BUS) @@ -146,6 +150,7 @@ object OverdriveThatMatters { AbstractRegistryAction.register(MOD_BUS) IMatterFunction.register(MOD_BUS) + ItemFilter.register(MOD_BUS) MRegistry.initialize(MOD_BUS) MatterManager.initialize(MOD_BUS) @@ -247,6 +252,9 @@ object OverdriveThatMatters { FORGE_BUS.addListener(EventPriority.NORMAL, MStructureTags::registerVillagerTrades) + FORGE_BUS.addListener(EventPriority.LOWEST, PlatePressBlockEntity::onReload) + FORGE_BUS.addListener(EventPriority.LOWEST, AbstractPoweredFurnaceBlockEntity.Companion::onReload) + if (isCuriosLoaded) { FORGE_BUS.addListener(EventPriority.NORMAL, ::onCuriosSlotModifiersUpdated) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt index cc5bd8684..ffe8b684a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/MatteryBlock.kt @@ -16,7 +16,6 @@ import net.minecraft.network.chat.Component import net.minecraft.network.chat.ComponentSerialization import net.minecraft.server.level.ServerLevel import net.minecraft.util.RandomSource -import net.minecraft.world.Containers import net.minecraft.world.InteractionResult import net.minecraft.world.MenuProvider import net.minecraft.world.entity.LivingEntity @@ -36,8 +35,6 @@ import net.minecraft.world.phys.shapes.VoxelShape import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.block.entity.IRedstoneControlled import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity -import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity -import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity.Companion import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.core.TooltipList import ru.dbotthepony.mc.otm.core.TranslatableComponent @@ -149,6 +146,9 @@ open class MatteryBlock(properties: Properties = DEFAULT_PROPERTIES) : Block(pro return getShapeForEachState(ArrayList(stateDefinition.properties), mapper) } + protected open val neverOpensAMenu: Boolean + get() = false + override fun useWithoutItem( blockState: BlockState, level: Level, @@ -156,18 +156,17 @@ open class MatteryBlock(properties: Properties = DEFAULT_PROPERTIES) : Block(pro ply: Player, blockHitResult: BlockHitResult ): InteractionResult { - if (this is EntityBlock && !level.isClientSide) { + if (!neverOpensAMenu && this is EntityBlock) { val tile = level.getBlockEntity(blockPos) if (tile is MenuProvider) { - ply.openMenu(tile) - return InteractionResult.CONSUME + if (!level.isClientSide) + ply.openMenu(tile) + + return InteractionResult.sidedSuccess(level.isClientSide) } } - if (this is EntityBlock && level.isClientSide) - return InteractionResult.SUCCESS - return super.useWithoutItem(blockState, level, blockPos, ply, blockHitResult) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt index 43d03cd67..3a0af5351 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/ExperienceStorage.kt @@ -117,6 +117,10 @@ class ExperienceStorage(val maxExperience: DoubleSupplier = DoubleSupplier { Dou experience = (nbt?.asDouble ?: 0.0).coerceAtLeast(0.0) } + private val liquidXPMilliBuckets: Int get() { + return (experience * XP_TO_LIQUID_RATIO).toInt() + } + override fun getTanks(): Int { return 1 } @@ -125,7 +129,7 @@ class ExperienceStorage(val maxExperience: DoubleSupplier = DoubleSupplier { Dou if (tank != 0) return FluidStack.EMPTY - return FluidStack(MFluids.LIQUID_XP, (experience * XP_TO_LIQUID_RATIO).toInt()) + return FluidStack(MFluids.LIQUID_XP, liquidXPMilliBuckets) } override fun getTankCapacity(tank: Int): Int { @@ -148,13 +152,13 @@ class ExperienceStorage(val maxExperience: DoubleSupplier = DoubleSupplier { Dou } override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack { - val actualDrain = maxDrain.coerceAtMost((experience * XP_TO_LIQUID_RATIO).toInt()).let { it / XP_TO_LIQUID_RATIO * XP_TO_LIQUID_RATIO } + val actualDrain = maxDrain.coerceAtMost(liquidXPMilliBuckets) if (actualDrain <= 0) return FluidStack.EMPTY if (action.execute()) - experience -= actualDrain / XP_TO_LIQUID_RATIO + experience -= actualDrain.toDouble() / XP_TO_LIQUID_RATIO return FluidStack(MFluids.LIQUID_XP, actualDrain) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt index 40f1bd7b9..1f53db539 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt @@ -6,13 +6,12 @@ import net.minecraft.world.level.block.state.BlockState import ru.dbotthepony.mc.otm.capability.energy import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.extractEnergy -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.math.Decimal abstract class MatteryPoweredBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(p_155228_, p_155229_, p_155230_) { - val batteryContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) - val batteryItemHandler = batteryContainer.handler(HandlerFilter.Dischargeable) + val batteryContainer = SlottedContainer.simple(1, AutomationFilters.DISCHARGABLE.filteredProvider, ::markDirtyFast).also(::addDroppableContainer) open val energy: IMatteryEnergyStorage? get() = null diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt index f1dc3a7d2..8808638ab 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/CargoCrateBlockEntity.kt @@ -17,12 +17,16 @@ import net.minecraft.world.level.gameevent.GameEvent import net.neoforged.neoforge.capabilities.Capabilities import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.capability.IQuickStackContainer +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.otmRandom +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import ru.dbotthepony.mc.otm.core.util.BlockLootTableHolder import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu +import ru.dbotthepony.mc.otm.menu.makeSlots import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.registry.game.MSoundEvents @@ -30,20 +34,28 @@ class CargoCrateBlockEntity( p_155229_: BlockPos, p_155230_: BlockState ) : MatteryDeviceBlockEntity(MBlockEntities.CARGO_CRATE, p_155229_, p_155230_) { - val container = MatteryContainer(this::setChanged, CAPACITY).also(::addDroppableContainer) + private inner class Slot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && loot.lootTable == null + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return super.canAutomationTakeItem(desired) && loot.lootTable == null + } + } + + val container = SlottedContainer.Builder() + .add(CAPACITY, ::Slot) + .onChanged(::setChanged) + .build() + .also(::addDroppableContainer) + + init { + exposeSideless(MatteryCapability.QUICK_STACK_CONTAINER, IQuickStackContainer.Simple(makeSlots(container, ::MatteryMenuSlot))) + } private var interactingPlayers = 0 - val handler = container.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return loot.lootTable == null - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return loot.lootTable == null - } - }) - override fun dropItems( oldBlockState: BlockState, level: ServerLevel, @@ -81,7 +93,7 @@ class CargoCrateBlockEntity( } init { - exposeGlobally(Capabilities.ItemHandler.BLOCK, handler) + exposeGlobally(Capabilities.ItemHandler.BLOCK, container) savetablesLevel.stateful(::container, INVENTORY_KEY) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt index d6b669f68..ffa775373 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/FluidTankBlockEntity.kt @@ -18,9 +18,12 @@ import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler import ru.dbotthepony.mc.otm.capability.fluid.BlockMatteryFluidHandler import ru.dbotthepony.mc.otm.capability.moveFluid import ru.dbotthepony.mc.otm.config.ItemsConfig -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilter +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.isNotSameAs import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu @@ -37,28 +40,42 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery } }), FluidStack.OPTIONAL_STREAM_CODEC.wrap())) - val fillInput = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) - val drainInput = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) - val output = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + private inner class FillSlot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + if (!super.canAutomationPlaceItem(itemStack)) + return false + + if (fluid.isEmpty) { + return itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.tanks > 0 } ?: false + } + + return itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.fill(fluid[0], IFluidHandler.FluidAction.SIMULATE) > 0 } ?: false + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return super.canAutomationTakeItem(desired) && !canAutomationPlaceItem(item) + } + } + + val inputContainer = SlottedContainer.Builder() + .add(DRAIN_TAG, AutomationFilters.DRAINABLE_FLUID_CONTAINERS.filteredProvider) + .add(FILL_TAG, ::FillSlot) + .onChanged(::markDirtyFast) + .build() + .also(::addDroppableContainer) + + val outputContainer = SlottedContainer.Builder() + .add(AutomationFilters.ONLY_OUT.simpleProvider) + .onChanged(::markDirtyFast) + .build() + .also(::addDroppableContainer) + + private val fillSlot = inputContainer[FILL_TAG] + private val drainSlot = inputContainer[DRAIN_TAG] val itemConfig = ConfigurableItemHandler( - input = CombinedItemHandler( - drainInput.handler(HandlerFilter.DrainableFluidContainers), - fillInput.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - if (fluid.isEmpty) { - return stack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.tanks > 0 } ?: false - } - - return stack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.fill(fluid[0], IFluidHandler.FluidAction.SIMULATE) > 0 } ?: false - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return !canInsert(slot, stack) - } - }) - ), - output = output.handler(HandlerFilter.OnlyOut), + input = inputContainer, + output = outputContainer, frontDefault = ItemHandlerMode.INPUT_OUTPUT, backDefault = ItemHandlerMode.INPUT_OUTPUT, leftDefault = ItemHandlerMode.INPUT_OUTPUT, @@ -71,20 +88,19 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery init { savetables.stateful(::fluid, FLUID_KEY) - savetables.stateful(::fillInput) - savetables.stateful(::drainInput) - savetables.stateful(::output) + savetables.stateful(::inputContainer) + savetables.stateful(::outputContainer) } private fun drainItem() { - val item = drainInput[0] + val item = drainSlot.item if (item.isNotEmpty) { val cap = (if (item.count == 1) item else item.copyWithCount(1)).getCapability(Capabilities.FluidHandler.ITEM) if (cap == null) { - if (output.consumeItem(item, simulate = false)) { - drainInput.setChanged(0) + if (outputContainer.consumeItem(item, simulate = false)) { + drainSlot.setChanged() } return @@ -95,17 +111,17 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery val moved0 = moveFluid(source = cap, destination = fluid) if (moved0.isNotEmpty) { - drainInput[0] = cap.container + drainSlot.item = cap.container - if (output.consumeItem(drainInput[0], simulate = false)) { - drainInput.setChanged(0) + if (outputContainer.consumeItem(drainSlot.item, simulate = false)) { + drainSlot.setChanged() } } } else { val moved0 = moveFluid(source = cap, destination = fluid, actuallyFill = false) if (moved0.isNotEmpty) { - if (output.consumeItem(cap.container, simulate = true)) { + if (outputContainer.consumeItem(cap.container, simulate = true)) { val cap1 = item.copyWithCount(1).getCapability(Capabilities.FluidHandler.ITEM) ?: throw ConcurrentModificationException() val moved1 = moveFluid(source = cap1, destination = fluid) @@ -114,9 +130,9 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery LOGGER.error("Error moving fluids in Fluid tank at $blockPos: moved $moved0 during simulation from ${cap.container}, moved $moved1 from ${cap1.container} during execution. This is likely a bug in OTM or other mod!") } else { item.count-- - drainInput.setChanged(0) + drainSlot.setChanged() - if (!output.consumeItem(cap1.container, simulate = false)) { + if (!outputContainer.consumeItem(cap1.container, simulate = false)) { LOGGER.error("Unable to insert ${cap1.container} into output slot of Fluid tank at $blockPos, popping item in world instead to avoid item loss! This is likely a bug in OTM or other mod!") (level as? ServerLevel)?.addFreshEntity(ItemEntity(level!!, blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble(), cap1.container)) } @@ -129,14 +145,14 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery } private fun fillItem() { - val item = fillInput[0] + val item = fillSlot.item if (item.isNotEmpty) { val cap = (if (item.count == 1) item else item.copyWithCount(1)).getCapability(Capabilities.FluidHandler.ITEM) if (cap == null) { - if (output.consumeItem(item, simulate = false)) { - fillInput.setChanged(0) + if (outputContainer.consumeItem(item, simulate = false)) { + fillSlot.setChanged() } return @@ -147,17 +163,17 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery val moved0 = moveFluid(source = fluid, destination = cap) if (moved0.isNotEmpty) { - fillInput[0] = cap.container + fillSlot.item = cap.container - if (output.consumeItem(fillInput[0], simulate = false)) { - fillInput.setChanged(0) + if (outputContainer.consumeItem(fillSlot.item, simulate = false)) { + fillSlot.setChanged() } } } else { val moved0 = moveFluid(source = fluid, destination = cap, actuallyDrain = false) if (moved0.isNotEmpty) { - if (output.consumeItem(cap.container, simulate = true)) { + if (outputContainer.consumeItem(cap.container, simulate = true)) { val cap1 = item.copyWithCount(1).getCapability(Capabilities.FluidHandler.ITEM) ?: throw ConcurrentModificationException() val moved1 = moveFluid(source = fluid, destination = cap1) @@ -166,9 +182,9 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery LOGGER.error("Error moving fluids in Fluid tank at $blockPos: moved $moved0 during simulation from ${cap.container}, moved $moved1 from ${cap1.container} during execution. This is likely a bug in OTM or other mod!") } else { item.count-- - fillInput.setChanged(0) + fillSlot.setChanged() - if (!output.consumeItem(cap1.container, simulate = false)) { + if (!outputContainer.consumeItem(cap1.container, simulate = false)) { LOGGER.error("Unable to insert ${cap1.container} into output slot of Fluid tank at $blockPos, popping item in world instead to avoid item loss! This is likely a bug in OTM or other mod!") (level as? ServerLevel)?.addFreshEntity(ItemEntity(level!!, blockPos.x.toDouble(), blockPos.y.toDouble(), blockPos.z.toDouble(), cap1.container)) } @@ -194,5 +210,8 @@ class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery companion object { const val FLUID_KEY = "fluid" private val LOGGER = LogManager.getLogger() + + private val FILL_TAG = SlottedContainer.tag() + private val DRAIN_TAG = SlottedContainer.tag() } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/GrillBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/GrillBlockEntity.kt index 7046c6a4a..529c4aaa6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/GrillBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/GrillBlockEntity.kt @@ -6,6 +6,7 @@ import net.minecraft.core.BlockPos import net.minecraft.core.HolderLookup import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerLevel import net.minecraft.world.Containers import net.minecraft.world.MenuProvider @@ -19,12 +20,16 @@ import net.minecraft.world.item.crafting.SmokingRecipe import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState import ru.dbotthepony.kommons.util.Delegate +import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.mc.otm.block.IBlockWithCustomName import ru.dbotthepony.mc.otm.block.decorative.GrillBlock import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.core.isNotEmpty @@ -38,47 +43,45 @@ import ru.dbotthepony.mc.otm.menu.decorative.GrillMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities class GrillBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(MBlockEntities.GRILL, blockPos, blockState), MenuProvider, IBlockWithCustomName { - val fuelSlot = object : MatteryContainer(this@GrillBlockEntity::markDirtyFast, 1) { - override fun getMaxStackSize(): Int { - return 4 - } - } + val fuelSlot = SlottedContainer.simple(1, FUEL_SLOT_PROVIDER, ::markDirtyFast) - val inputSlots = object : MatteryContainer(this@GrillBlockEntity::markDirtyFast, SLOTS) { - override fun getMaxStackSize(): Int { - return 1 - } + private inner class InputSlot(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override val maxStackSize: Int + get() = 1 - private fun clearSlot(slot: Int) { + + private fun clearSlot() { inputProgress[slot] = 0 outputs[slot] = null + outputsRecipeNames[slot] = null activeSlots.rem(slot) } - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) + override fun notifyChanged(old: ItemStack) { + super.notifyChanged(old) - if (new.isEmpty || new.count > 1) { - clearSlot(slot) + if (item.isEmpty || item.count > 1) { + clearSlot() } else { val level = level if (level == null) { - clearSlot(slot) + clearSlot() return } - val input = SingleRecipeInput(new) + val input = SingleRecipeInput(item) val result = level.recipeManager .byType(RecipeType.SMOKING) .firstOrNull { it.value.matches(input, level) } if (result == null) { - clearSlot(slot) + clearSlot() } else { - if (outputs[slot] != result.value) + if (outputsRecipeNames[slot] != result.id) inputProgress[slot] = 0 + outputsRecipeNames[slot] = result.id outputs[slot] = result.value activeSlots.add(slot) } @@ -86,16 +89,18 @@ class GrillBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBloc } } + val inputSlots = SlottedContainer.simple(SLOTS, ::InputSlot, ::markDirtyFast) override var customDisplayName: Component? = null - override fun createMenu(p_39954_: Int, p_39955_: Inventory, p_39956_: Player): AbstractContainerMenu { - return GrillMenu(p_39954_, p_39955_, this) + override fun createMenu(containerID: Int, inventory: Inventory, player: Player): AbstractContainerMenu { + return GrillMenu(containerID, inventory, this) } override fun getDisplayName(): Component { return customDisplayName ?: level?.getBlockState(blockPos)?.block?.name ?: TextComponent("null at $blockPos") } + private val outputsRecipeNames = arrayOfNulls(SLOTS) private val outputs = arrayOfNulls(SLOTS) private val activeSlots = IntArrayList() private val inputProgress = IntArray(SLOTS) @@ -132,9 +137,12 @@ class GrillBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBloc savetables.stateful(::fuelSlot) savetables.stateful(::experience) + outputsRecipeNames.fill(null) + // TODO: could have been done as list (and get more space efficient storage), but we use an array, oh well for (i in inputProgress.indices) { savetables.codec(Delegate.Of({ inputProgress[i] }, { inputProgress[i] = it }), progressCodec, "inputProgress${i}") + savetables.codecNullable(Delegate.Of({ outputsRecipeNames[i] }, { outputsRecipeNames[i] = it }), ResourceLocation.CODEC, "recipeName${i}") } // addDroppableContainer(inputSlots) @@ -207,5 +215,6 @@ class GrillBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBloc const val SLOTS = 6 private val progressCodec = Codec.INT.minRange(0) + val FUEL_SLOT_PROVIDER = ContainerSlot.Simple(maxStackSize = 4, filter = AutomationFilters.CHEMICAL_FUEL) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt index 59733f0ed..321fe0f6d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/decorative/PainterBlockEntity.kt @@ -18,8 +18,8 @@ import net.neoforged.neoforge.capabilities.Capabilities import net.neoforged.neoforge.fluids.FluidStack import net.neoforged.neoforge.fluids.capability.IFluidHandler import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.immutableMap import ru.dbotthepony.mc.otm.core.isNotEmpty @@ -34,7 +34,39 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe return PainterMenu(containerID, inventory, this) } - val dyeInput = MatteryContainer(this::markDirtyFast, 1) + private inner class InputSlot(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + if (!super.canAutomationPlaceItem(itemStack)) + return false + + if (waterStored() < MAX_WATER_STORAGE) { + itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.let { + val drain = it.drain(FluidStack(Fluids.WATER, MAX_WATER_STORAGE - waterStored()), IFluidHandler.FluidAction.SIMULATE) + + if (drain.isNotEmpty) { + return true + } + } + } + + val dye = DyeColor.getColor(itemStack) ?: return false + return dyeStored(dye) + HUE_PER_ITEM <= MAX_STORAGE + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return false + } + + override fun modifyAutomationPlaceCount(itemStack: ItemStack): Int { + if (itemStack.getCapability(Capabilities.FluidHandler.ITEM) != null) + return 1 + + val dye = DyeColor.getColor(itemStack) ?: return 0 + return itemStack.count.coerceAtMost((MAX_STORAGE - dyeStored(dye)) / HUE_PER_ITEM - item.count) + } + } + + val dyeInput = SlottedContainer.simple(1, ::InputSlot, ::markDirtyFast) // null - water val dyeStored = Object2IntArrayMap() @@ -108,34 +140,7 @@ class PainterBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDe markDirtyFast() } - val config = ConfigurableItemHandler(input = dyeInput.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - if (waterStored() < MAX_WATER_STORAGE) { - stack.getCapability(Capabilities.FluidHandler.ITEM)?.let { - val drain = it.drain(FluidStack(Fluids.WATER, MAX_WATER_STORAGE - waterStored()), IFluidHandler.FluidAction.SIMULATE) - - if (drain.isNotEmpty) { - return true - } - } - } - - val dye = DyeColor.entries.firstOrNull { stack.`is`(it.tag) } ?: return false - return dyeStored(dye) + HUE_PER_ITEM <= MAX_STORAGE - } - - override fun modifyInsertCount(slot: Int, stack: ItemStack, existing: ItemStack, simulate: Boolean): Int { - if (!ItemStack.isSameItemSameComponents(stack, existing)) - return super.modifyInsertCount(slot, stack, existing, simulate) - - val dye = DyeColor.entries.firstOrNull { stack.`is`(it.tag) } ?: return 0 - return stack.count.coerceAtMost((MAX_STORAGE - dyeStored(dye)) / HUE_PER_ITEM - existing.count) - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return false - } - })) + val config = ConfigurableItemHandler(input = dyeInput) fun waterStored(): Int { return dyeStored.getInt(null) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt index dd474603b..91e8d4d20 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt @@ -21,39 +21,37 @@ 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.MatteryContainer -import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.matter.MatterBottlerMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.countingLazy import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode import java.util.function.BooleanSupplier class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.MATTER_BOTTLER, blockPos, blockState) { - val upgrades = UpgradeContainer(3, UpgradeType.BASIC_MATTER, BooleanSupplier { blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.IDLE }, ::markDirtyFast) + val upgrades = UpgradeContainer(3, UpgradeType.BASIC_MATTER, BooleanSupplier { this.blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.IDLE }, ::markDirtyFast) override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::markDirtyFast, upgrades.transform(MachinesConfig.MatterBottler.VALUES))) val energyConfig = ConfigurableEnergy(energy) - private inner class Container : MatteryContainer(3) { - init { - addDroppableContainer(this) - } - - override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { - return 1 - } - - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - markDirtyFast() - updateBlockState() + private inner class BottlingSlot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && isBottling && itemStack.getCapability(MatteryCapability.MATTER_ITEM)?.receiveMatterChecked(Decimal.ONE, true)?.isPositive == true } } - val unbottling: MatteryContainer = Container() - val bottling: MatteryContainer = Container() + private inner class UnBottlingSlot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && !isBottling && itemStack.getCapability(MatteryCapability.MATTER_ITEM)?.extractMatterChecked(Decimal.ONE, true)?.isPositive == true + } + } + + val unbottling = SlottedContainer.simple(3, ::UnBottlingSlot, ::updateBlockState) + val bottling = SlottedContainer.simple(3, ::BottlingSlot, ::updateBlockState) var isBottling: Boolean = true set(value) { @@ -63,11 +61,11 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) : this.markDirtyFast() if (value) { - inputHandler.parent = bottlingHandler - outputHandler.parent = unbottlingHandler + inputHandler.parent = bottling + outputHandler.parent = unbottling } else { - inputHandler.parent = unbottlingHandler - outputHandler.parent = bottlingHandler + inputHandler.parent = unbottling + outputHandler.parent = bottling } updateBlockState() @@ -79,25 +77,13 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) : this.markDirtyFast() } - val bottlingHandler = bottling.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return isBottling && stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { it.matterFlow.input && it.missingMatter.isPositive } ?: false - } - }) - - val unbottlingHandler = unbottling.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return !isBottling && stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { it.matterFlow.output && it.storedMatter.isPositive } ?: false - } - }) - - private val inputHandler = ProxiedItemHandler(bottlingHandler) - private val outputHandler = ProxiedItemHandler(unbottlingHandler) + private val inputHandler = ProxiedItemHandler(bottling) + private val outputHandler = ProxiedItemHandler(unbottling) val itemConfig = ConfigurableItemHandler( input = inputHandler, output = outputHandler, - battery = batteryItemHandler + battery = batteryContainer ) val matter: ProfiledMatterStorage = ProfiledMatterStorage(object : MatterStorageImpl(this@MatterBottlerBlockEntity::markDirtyFast, FlowDirection.BI_DIRECTIONAL, upgrades.matterCapacity(MachinesConfig.MatterBottler.VALUES::matterCapacity)) { @@ -141,12 +127,12 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) : matterNode.isValid = false } - private fun updateBlockState(container: MatteryContainer) { + private fun updateBlockState(container: SlottedContainer) { val level = level as? ServerLevel ?: return var state = blockState - for (i in 0 .. 2) { - val desired = !container.getItem(i).isEmpty && container.getItem(i).getCapability(MatteryCapability.MATTER_ITEM) != null + for ((i, slot) in container.slotIterator().withIndex()) { + val desired = !slot.isEmpty && slot.item.getCapability(MatteryCapability.MATTER_ITEM) != null if (state.getValue(MatterBottlerBlock.SLOT_PROPERTIES[i]) != desired) { state = state.setValue(MatterBottlerBlock.SLOT_PROPERTIES[i], desired) @@ -154,26 +140,156 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) : } if (state !== blockState) { - level.setBlock(blockPos, state, Block.UPDATE_CLIENTS) + level.setBlock(blockPos, state, Block.UPDATE_ALL) } } private fun updateBlockState() { + markDirtyFast() if (isBottling) updateBlockState(bottling) else updateBlockState(unbottling) } + private val workerState by countingLazy(blockStateChangesCounter) { + this.blockState.getValue(WorkerState.SEMI_WORKER_STATE) + } + private fun blockstateToWorking() { - if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.WORKING) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_CLIENTS) + if (workerState !== WorkerState.WORKING) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING), Block.UPDATE_ALL) } } private fun blockstateToIdle() { - if (blockState.getValue(WorkerState.SEMI_WORKER_STATE) !== WorkerState.IDLE) { - level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_CLIENTS) + if (workerState !== WorkerState.IDLE) { + level?.setBlock(blockPos, blockState.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE), Block.UPDATE_ALL) + } + } + + private fun tickBottling() { + var isWorking = false + var hasCapacitors = false + + for (slot in bottling.slotIterator()) { + val item = slot.item + val capability = item.getCapability(MatteryCapability.MATTER_ITEM) ?: continue + + if (!capability.receiveMatterChecked(Decimal.LONG_MAX_VALUE, true).isPositive) { + unbottling.consumeItem(item, false) + slot.setChanged() + continue + } + + hasCapacitors = true + initialCapacity = initialCapacity ?: capability.storedMatter + val rate = MachinesConfig.MatterBottler.RATE * (1.0 + upgrades.speedBonus) + val energyRate = MachinesConfig.MatterBottler.VALUES.energyConsumption * (1.0 + upgrades.speedBonus) + + if (matter.storedMatter < rate) { + val toExtract = matter.missingMatter + .coerceAtMost(rate * 200) + .coerceAtMost(capability.missingMatter - matter.storedMatter) + + matter.receiveMatter(matterNode.graph.extractMatter(toExtract, false), false) + } + + if (matter.storedMatter.isPositive) { + val energyRatio = if (energyRate <= Decimal.ZERO) Decimal.ONE else energy.extractEnergy(energyRate, true) / energyRate + val matterRatio = matter.extractMatter(capability.receiveMatter(rate.coerceAtMost(matter.storedMatter), true), true) / rate + + val minRatio = minOf(matterRatio, energyRatio) + + if (minRatio > Decimal.ZERO) { + isWorking = true + energy.extractEnergy(energyRate * minRatio, false) + matter.extractMatter(capability.receiveMatter(rate * minRatio, false), false) + workProgress = ((capability.storedMatter - initialCapacity!!) / capability.maxStoredMatter).toFloat() + slot.setChanged() + } + } else { + if (spitItemsWhenCantWork) { + unbottling.consumeItem(item, false) + slot.setChanged() + } + } + + break + } + + if (isWorking) { + blockstateToWorking() + } else { + blockstateToIdle() + } + + if (!hasCapacitors) { + matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false) + initialCapacity = null + workProgress = 0f + } + } + + private fun tickUnbottling() { + matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false) + + if (!matter.missingMatter.isPositive) { + if (spitItemsWhenCantWork) { + for (slot in unbottling.slotIterator()) { + bottling.consumeItem(slot.item, false) + slot.setChanged() + } + } + + blockstateToIdle() + return + } + + var any = false + var hasCapacitors = false + + for (slot in unbottling.slotIterator()) { + val item = slot.item + val it = item.getCapability(MatteryCapability.MATTER_ITEM) ?: continue + + if (!it.extractMatterChecked(Decimal.LONG_MAX_VALUE, true).isPositive) { + bottling.consumeItem(item, false) + slot.setChanged() + continue + } + + initialCapacity = initialCapacity ?: it.storedMatter + + hasCapacitors = true + val rate = MachinesConfig.MatterBottler.RATE * (1.0 + upgrades.speedBonus) + val energyRate = MachinesConfig.MatterBottler.VALUES.energyConsumption * (1.0 + upgrades.speedBonus) + + val energyRatio = if (energyRate <= Decimal.ZERO) Decimal.ONE else energy.extractEnergy(energyRate, true) / energyRate + val matterRatio = matter.receiveMatter(it.extractMatterChecked(rate, true), true) / rate + + val minRatio = minOf(energyRatio, matterRatio) + + if (minRatio > Decimal.ZERO) { + any = true + energy.extractEnergy(energyRate * energyRatio, false) + matter.receiveMatter(it.extractMatterChecked(rate * minRatio, false), false) + + workProgress = 1f - (it.storedMatter / initialCapacity!!).toFloat() + } + + break + } + + if (any) { + blockstateToWorking() + } else { + blockstateToIdle() + } + + if (!hasCapacitors) { + initialCapacity = null + workProgress = 0f } } @@ -186,105 +302,9 @@ class MatterBottlerBlockEntity(blockPos: BlockPos, blockState: BlockState) : } if (isBottling) { - var any = false - var idle = false - - for (slot in bottling.slotIterator()) { - val item = slot.item - item.getCapability(MatteryCapability.MATTER_ITEM)?.let { - if (!it.missingMatter.isPositive) { - unbottling.consumeItem(item, false) - slot.setChanged() - } else { - any = true - initialCapacity = initialCapacity ?: it.storedMatter - val rate = MachinesConfig.MatterBottler.RATE * (1.0 + upgrades.speedBonus) - - if (matter.storedMatter < rate) { - matter.receiveMatter(matterNode.graph.extractMatter(matter.missingMatter - .coerceAtMost(rate * 200) - .coerceAtMost(it.missingMatter - matter.storedMatter), false), false) - } - - if (matter.storedMatter.isPositive) { - matter.extractMatter(it.receiveMatter(rate.coerceAtMost(matter.storedMatter), false), false) - - if (!it.missingMatter.isPositive) { - initialCapacity = null - workProgress = 0f - } else { - workProgress = ((it.storedMatter - initialCapacity!!) / it.maxStoredMatter).toFloat() - } - } else { - idle = true - - if (spitItemsWhenCantWork) { - unbottling.consumeItem(item, false) - slot.setChanged() - } - } - } - } - - if (any) { - break - } - } - - if (any && !idle) { - blockstateToWorking() - } else { - matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false) - blockstateToIdle() - } + tickBottling() } else { - matter.extractMatter(matterNode.graph.receiveMatter(matter.storedMatter, false), false) - - if (!matter.missingMatter.isPositive) { - if (spitItemsWhenCantWork) { - for (slot in unbottling.slotIterator()) { - bottling.consumeItem(slot.item, false) - slot.setChanged() - } - } - - blockstateToIdle() - return - } - - var any = false - - for (slot in unbottling.slotIterator()) { - val item = slot.item - - item.getCapability(MatteryCapability.MATTER_ITEM)?.let { - if (!it.storedMatter.isPositive) { - bottling.consumeItem(item, false) - slot.setChanged() - } else { - any = true - initialCapacity = initialCapacity ?: it.storedMatter - matter.receiveMatter(it.extractMatter(MachinesConfig.MatterBottler.RATE, false), false) - - if (!it.storedMatter.isPositive) { - initialCapacity = null - workProgress = 0f - } else { - workProgress = 1f - (it.storedMatter / initialCapacity!!).toFloat() - } - } - } - - if (any) { - break - } - } - - if (any) { - blockstateToWorking() - } else { - blockstateToIdle() - } + tickUnbottling() } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt index a9df1d08f..40d188eb6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterCapacitorBankBlockEntity.kt @@ -16,16 +16,15 @@ import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode import ru.dbotthepony.mc.otm.menu.matter.MatterCapacitorBankMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities -class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity( - MBlockEntities.MATTER_CAPACITOR_BANK, p_155229_, p_155230_), IMatterStorage { +class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.MATTER_CAPACITOR_BANK, p_155229_, p_155230_), IMatterStorage { var gaugeLevel by syncher.float() private set @@ -40,10 +39,9 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) var summ = Decimal.ZERO for (stack in container) - if (!stack.isEmpty) - stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { - summ += it.storedMatter - } + stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { + summ += it.storedMatter + } return summ } @@ -51,19 +49,35 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) throw UnsupportedOperationException() } - override val maxStoredMatter: Decimal - get() { + override val maxStoredMatter: Decimal get() { var summ = Decimal.ZERO for (stack in container) - if (!stack.isEmpty) - stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { - summ += it.maxStoredMatter - } + stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { + summ += it.maxStoredMatter + } return summ } + private fun updateGaugeLevel() { + var stored = Decimal.ZERO + var maxStored = Decimal.ZERO + + for (stack in container) { + val cap = stack.getCapability(MatteryCapability.MATTER_ITEM) ?: continue + stored += cap.storedMatter + maxStored += cap.maxStoredMatter + } + + gaugeLevel = stored.percentage(maxStored) + } + + override fun tick() { + super.tick() + updateGaugeLevel() + } + override fun receiveMatter(howMuch: Decimal, simulate: Boolean): Decimal { if (!howMuch.isPositive) return Decimal.ZERO @@ -73,21 +87,18 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) var summ = Decimal.ZERO for (stack in container) { - if (!stack.isEmpty) { - stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { - val diff = it.receiveMatterChecked(howMuch, simulate) - summ += diff - howMuch -= diff - } + val it = stack.getCapability(MatteryCapability.MATTER_ITEM) ?: continue + val diff = it.receiveMatterChecked(howMuch, simulate) + summ += diff + howMuch -= diff - if (howMuch.isZero) { - break - } + if (howMuch.isZero) { + break } } - if (summ.isPositive && !simulate) { - gaugeLevel = storedMatter.percentage(maxStoredMatter) + if (!simulate && !summ.isZero) { + markDirtyFast() } return summ @@ -102,21 +113,18 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) var summ = Decimal.ZERO for (stack in container) { - if (!stack.isEmpty) { - stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { - val diff = it.extractMatterChecked(howMuch, simulate) - summ += diff - howMuch -= diff - } + val it = stack.getCapability(MatteryCapability.MATTER_ITEM) ?: continue + val diff = it.extractMatterChecked(howMuch, simulate) + summ += diff + howMuch -= diff - if (howMuch.isZero) { - break - } + if (howMuch.isZero) { + break } } - if (summ.isPositive && !simulate) { - gaugeLevel = storedMatter.percentage(maxStoredMatter) + if (!simulate && !summ.isZero) { + markDirtyFast() } return summ @@ -125,31 +133,38 @@ class MatterCapacitorBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) override val matterFlow: FlowDirection get() = FlowDirection.BI_DIRECTIONAL - val container = object : MatteryContainer(::markDirtyFast, BatteryBankBlockEntity.CAPACITY) { - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) - capacitorStatus[slot].value = new.getCapability(MatteryCapability.MATTER_ITEM) != null - gaugeLevel = storedMatter.percentage(maxStoredMatter) + private inner class Slot(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override fun notifyChanged(old: ItemStack) { + super.notifyChanged(old) + + capacitorStatus[slot].value = item.getCapability(MatteryCapability.MATTER_ITEM) != null } - override fun getMaxStackSize(): Int = 1 - }.also(::addDroppableContainer) - - val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getCapability(MatteryCapability.MATTER_ITEM) != null + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && itemStack.getCapability(MatteryCapability.MATTER_ITEM) != null } - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - stack.getCapability(MatteryCapability.MATTER_ITEM)?.let { + override fun canAutomationTakeItem(desired: Int): Boolean { + item.getCapability(MatteryCapability.MATTER_ITEM)?.let { if (it.storedMatter.isPositive) { return false } } - return true + return super.canAutomationTakeItem(desired) } - })) + + override val maxStackSize: Int + get() = 1 + } + + private fun containerUpdated() { + markDirtyFast() + updateGaugeLevel() + } + + val container = SlottedContainer.simple(BatteryBankBlockEntity.CAPACITY, ::Slot, ::containerUpdated).also(::addDroppableContainer) + val itemConfig = ConfigurableItemHandler(inputOutput = container) val capacitorStatus = immutableList(BatteryBankBlockEntity.CAPACITY) { syncher.boolean(false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt index c4d753c96..2d839bc95 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt @@ -21,8 +21,10 @@ 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.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.otmRandom import ru.dbotthepony.mc.otm.data.codec.DecimalCodec @@ -72,22 +74,23 @@ class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState) savetables.stateful(::matter, MATTER_STORAGE_KEY) } + private inner class InputSlot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && MatterManager.canDecompose(itemStack) + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return false + } + } + // вход, выход - val inputContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) - val outputContainer = MatteryContainer(::markDirtyFast, 2).also(::addDroppableContainer) + val inputContainer = SlottedContainer.simple(1, ::InputSlot, ::markDirtyFast).also(::addDroppableContainer) + val outputContainer = SlottedContainer.simple(2, AutomationFilters.ONLY_OUT.simpleProvider, ::markDirtyFast).also(::addDroppableContainer) val itemConfig = ConfigurableItemHandler( - input = inputContainer.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return MatterManager.canDecompose(stack) - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return false - } - }), - - output = outputContainer.handler(HandlerFilter.OnlyOut) + input = inputContainer, + output = outputContainer ) init { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt index a09006236..e0e4857ef 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterEntanglerBlockEntity.kt @@ -8,6 +8,7 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.item.crafting.CraftingInput +import net.minecraft.world.item.crafting.RecipeManager import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState import net.neoforged.neoforge.capabilities.Capabilities @@ -24,19 +25,28 @@ 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.IEnhancedCraftingContainer +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.core.SimpleCache +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.forEach +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.collect.toList import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.container.ItemStackKey +import ru.dbotthepony.mc.otm.container.asKey import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.data.codec.minRange import ru.dbotthepony.mc.otm.graph.matter.MatterNode import ru.dbotthepony.mc.otm.menu.matter.MatterEntanglerMenu +import ru.dbotthepony.mc.otm.recipe.IMatterEntanglerRecipe import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.registry.game.MRecipes +import java.time.Duration -class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryWorkerBlockEntity( - MBlockEntities.MATTER_ENTANGLER, blockPos, blockState, Job.CODEC) { +class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryWorkerBlockEntity(MBlockEntities.MATTER_ENTANGLER, blockPos, blockState, Job.CODEC) { class Job(itemStack: ItemStack, val matter: Decimal, ticks: Double, experience: Float) : ItemJob(itemStack, ticks, MachinesConfig.MATTER_ENTANGLER.energyConsumption, experience = experience) { val matterPerTick = matter / ticks @@ -60,36 +70,67 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M val experience = ExperienceStorage(MachinesConfig.MATTER_ENTANGLER::maxExperienceStored).also(::addNeighbourListener) val energyConfig = ConfigurableEnergy(energy) - val inputs = object : MatteryCraftingContainer(::itemContainerUpdated, 3, 3) { - override fun getMaxStackSize(): Int { - return 1 + private var recipeCache: Collection = listOf() + private var seenRecipeManager: RecipeManager? = null + + private fun getRecipes(): Collection { + val level = level!! + val manager = level.recipeManager + + if (seenRecipeManager !== manager) { + seenRecipeManager = manager + val input = inputs.asPositionedCraftInput() + + recipeCache = manager.byType(MRecipes.MATTER_ENTANGLER) + .iterator() + .map { it.value } + .filter { it.preemptivelyMatches(input, level, 3, 3) } + .toList() } + + return recipeCache } - val output = object : MatteryContainer(::itemContainerUpdated, 1) { - override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { - return Int.MAX_VALUE + private inner class InputSlot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + // may get stalled on /reload command for up to a minute + // shouldn't cause major issues through, since /reload is not something you frequently be executing + val insertCache = SimpleCache(Duration.ofMinutes(1)) + + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + if (!super.canAutomationPlaceItem(itemStack)) + return false + + val level = level ?: return false + + return insertCache.get(itemStack.asKey()) { + val list = container.toList() + list[slot] = itemStack + val shadow = CraftingInput.ofPositioned(3, 3, list) + getRecipes().any { it.preemptivelyMatches(shadow, level, 3, 3) } + } } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return false + } + + override val maxStackSize: Int + get() = 1 } + private fun inputsChanged() { + inputs.slotIterator().forEach { (it as InputSlot).insertCache.invalidateAll() } + seenRecipeManager = null + recipeCache = listOf() + setChanged() + } + + val inputs = IEnhancedCraftingContainer.Wrapper(SlottedContainer.simple(3 * 3, ::InputSlot, ::inputsChanged), 3, 3) + val output = SlottedContainer.simple(1, AutomationFilters.ONLY_OUT.unlimitedSimpleProvider, ::markDirtyFast) + val itemConfig = ConfigurableItemHandler( - input = inputs.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - val list = inputs.toList() - list[slot] = stack - val shadow = CraftingInput.ofPositioned(3, 3, list) - - return (level ?: return false) - .recipeManager - .byType(MRecipes.MATTER_ENTANGLER) - .any { it.value.preemptivelyMatches(shadow, level!!, 3, 3) } - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return false - } - }), - output = output.handler(HandlerFilter.OnlyOut) + input = inputs.parent, + output = output ) init { @@ -98,7 +139,7 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M savetables.stateful(::energy, ENERGY_KEY) savetables.stateful(::matter, MATTER_STORAGE_KEY) savetables.stateful(::upgrades) - savetables.stateful(::inputs) + savetables.stateful(inputs::parent, "inputs") savetables.stateful(::output) savetables.stateful(::experience) @@ -124,7 +165,7 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M 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) + matter.receiveMatter(node.graph.extractMatter(status.job.matterPerTick.coerceIn(Decimal.TEN, matter.missingMatter), false), false) } status.scale(matter.extractMatter(required, false) / required) @@ -150,7 +191,7 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M if (!energy.batteryLevel.isPositive) return JobContainer.noEnergy() - val inputs = CraftingInput.of(3, 3, inputs.toList()) + val inputs = this.inputs.asCraftInput() val recipe = (level ?: return JobContainer.failure()) .recipeManager diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt index 2ce185aca..49ef44425 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReconstructorBlockEntity.kt @@ -24,9 +24,9 @@ import ru.dbotthepony.mc.otm.capability.matter.PatternState 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.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.otmRandom import ru.dbotthepony.mc.otm.core.registryName @@ -37,9 +37,35 @@ import ru.dbotthepony.mc.otm.menu.matter.MatterReconstructorMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import java.util.function.BooleanSupplier -class MatterReconstructorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity( - MBlockEntities.MATTER_RECONSTRUCTOR, blockPos, blockState) { - val repairContainer = MatteryContainer(::containerChanged, 1).also(::addDroppableContainer) +class MatterReconstructorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.MATTER_RECONSTRUCTOR, blockPos, blockState) { + private inner class Slot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + if (!super.canAutomationPlaceItem(itemStack) || !itemStack.isRepairable || !itemStack.isDamaged) { + return false + } + + if (MachinesConfig.MatterReconstructor.ALLOW_TO_SKIP_ANVIL && !MachinesConfig.MatterReconstructor.ONLY_ANVIL && MatterManager.get(itemStack.item).hasMatterValue) { + return true + } + + return matterNode.graph + .patterns + .filter { itemStack.item.isValidRepairItem(itemStack, ItemStack(it.item, 1)) } + .findFirst().orElse(null).let { + if (it == null) { + IMatterValue.ZERO + } else { + MatterManager.get(it.item) + } + }.hasMatterValue + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return super.canAutomationTakeItem(desired) && (progressPerTick <= 0.0 || matterPerTick <= Decimal.ZERO) + } + } + + val repairContainer = SlottedContainer.simple(1, ::Slot, ::rescan).also(::addDroppableContainer) private var matterPerTick = Decimal.ZERO private var progressPerTick = 0.0 @@ -67,15 +93,15 @@ class MatterReconstructorBlockEntity(blockPos: BlockPos, blockState: BlockState) } override fun onPatternAdded(state: PatternState) { - containerChanged() + rescan() } override fun onPatternRemoved(state: PatternState) { - containerChanged() + rescan() } override fun onPatternUpdated(newState: PatternState, oldState: PatternState) { - containerChanged() + rescan() } } @@ -99,34 +125,7 @@ class MatterReconstructorBlockEntity(blockPos: BlockPos, blockState: BlockState) val energyConfig = ConfigurableEnergy(energy) val itemConfig = ConfigurableItemHandler( - inputOutput = repairContainer.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - if (!stack.isRepairable || !stack.isDamaged) { - return false - } - - if (MachinesConfig.MatterReconstructor.ALLOW_TO_SKIP_ANVIL && !MachinesConfig.MatterReconstructor.ONLY_ANVIL) { - if (MatterManager.get(stack.item).hasMatterValue) { - return true - } - } - - return matterNode.graph - .patterns - .filter { stack.item.isValidRepairItem(stack, ItemStack(it.item, 1)) } - .findFirst().orElse(null).let { - if (it == null) { - IMatterValue.ZERO - } else { - MatterManager.get(it.item) - } - }.hasMatterValue - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return progressPerTick <= 0.0 || matterPerTick <= Decimal.ZERO - } - }) + inputOutput = repairContainer ) override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { @@ -145,7 +144,7 @@ class MatterReconstructorBlockEntity(blockPos: BlockPos, blockState: BlockState) private var changeset = 0 - private fun containerChanged() { + private fun rescan() { matterPerTick = Decimal.ZERO progressPerTick = 0.0 @@ -196,7 +195,6 @@ class MatterReconstructorBlockEntity(blockPos: BlockPos, blockState: BlockState) } } - @Suppress("name_shadowing") val matter = MatterManager.get(found.item) * 2 progressPerTick = (item.maxDamage / matter.complexity) / MachinesConfig.MatterReconstructor.DIVISOR diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt index ce4f69727..e93ebf9e9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt @@ -21,9 +21,10 @@ 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.MatteryContainer -import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.nextDecimal import ru.dbotthepony.mc.otm.core.otmRandom import ru.dbotthepony.mc.otm.graph.matter.MatterGraph import ru.dbotthepony.mc.otm.item.matter.MatterDustItem @@ -33,9 +34,7 @@ import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.data.codec.minRange import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode -class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) - : MatteryWorkerBlockEntity(MBlockEntities.MATTER_RECYCLER, blockPos, blockState, RecyclerJob.CODEC) { - +class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryWorkerBlockEntity(MBlockEntities.MATTER_RECYCLER, blockPos, blockState, RecyclerJob.CODEC) { class RecyclerJob(ticks: Double, powerUsage: Decimal, var totalMatter: Decimal) : Job(ticks, powerUsage) { companion object { val CODEC: Codec by lazy { @@ -48,20 +47,22 @@ class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) override val upgrades = makeUpgrades(3, UpgradeType.BASIC_MATTER) val matter = ProfiledMatterStorage(MatterStorageImpl(::matterLevelUpdated, FlowDirection.OUTPUT, upgrades.matterCapacity(MachinesConfig.MatterRecycler.VALUES::matterCapacity))) - val container = MatteryContainer(::itemContainerUpdated, 1).also(::addDroppableContainer) + private class Slot(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override fun canAutomationTakeItem(desired: Int): Boolean { + return false + } + + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && itemStack.item is MatterDustItem + } + } + + val container = SlottedContainer.simple(1, ::Slot, ::itemContainerUpdated).also(::addDroppableContainer) val matterNode = SimpleMatterNode(matter = matter) override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MatterRecycler.VALUES))) - val itemConfig = ConfigurableItemHandler(input = container.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.item is MatterDustItem - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return false - } - })) + val itemConfig = ConfigurableItemHandler(container) val energyConfig = ConfigurableEnergy(energy) @@ -137,7 +138,7 @@ class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) else if (matter.receiveMatter(toReceive, true) != toReceive) return status.noMatter() - matter.receiveMatter(toReceive * 0.4 + level!!.otmRandom.nextDouble() * 0.6, false) + matter.receiveMatter(toReceive * level!!.otmRandom.nextDecimal(BASE_RECEIVE, Decimal.ONE), false) job.totalMatter -= toReceive } @@ -151,4 +152,8 @@ class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) matter.extractMatter(received, false) } } + + companion object { + private val BASE_RECEIVE = Decimal("0.4") + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt index ac9f9c181..d472058ae 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt @@ -25,8 +25,8 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.capability.matter.* import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.otmRandom import ru.dbotthepony.mc.otm.data.codec.DecimalCodec @@ -71,11 +71,11 @@ class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : override val upgrades = makeUpgrades(3, UpgradeType.REPLICATOR) override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MATTER_REPLICATOR))) val matter = ProfiledMatterStorage(MatterStorageImpl(::matterLevelUpdated, FlowDirection.INPUT, upgrades.matterCapacity(MachinesConfig.MATTER_REPLICATOR::matterCapacity))) - val outputContainer = MatteryContainer(::itemContainerUpdated, 3).also(::addDroppableContainer) - val dustContainer = MatteryContainer(::itemContainerUpdated, 2).also(::addDroppableContainer) + val outputContainer = SlottedContainer.simple(3, AutomationFilters.ONLY_OUT.simpleProvider, ::itemContainerUpdated).also(::addDroppableContainer) + val dustContainer = SlottedContainer.simple(2, AutomationFilters.ONLY_OUT.simpleProvider, ::itemContainerUpdated).also(::addDroppableContainer) val energyConfig = ConfigurableEnergy(energy) - val itemConfig = ConfigurableItemHandler(output = CombinedItemHandler(outputContainer.handler(HandlerFilter.OnlyOut), dustContainer.handler(HandlerFilter.OnlyOut))) + val itemConfig = ConfigurableItemHandler(output = CombinedItemHandler(outputContainer, dustContainer)) val matterNode = object : MatterNode() { override fun getMatterHandler(): IMatterStorage { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt index 2378e78ce..be8473c8c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt @@ -20,8 +20,8 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.capability.matter.PatternState import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.matter.MatterScannerMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.graph.matter.MatterNode @@ -34,19 +34,20 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryWorkerBlockEntity(MBlockEntities.MATTER_SCANNER, p_155229_, p_155230_, ItemJob.CODEC) { override val upgrades = makeUpgrades(2, UpgradeType.BASIC) - val container = MatteryContainer(::itemContainerUpdated, 1).also(::addDroppableContainer) override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, upgrades.transform(MachinesConfig.MATTER_SCANNER))) - val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return MatterManager.canDecompose(stack) + private inner class Slot(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && MatterManager.canDecompose(itemStack) } - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return jobEventLoops[0].isIdling + override fun canAutomationTakeItem(desired: Int): Boolean { + return jobEventLoops[0].isIdling && super.canAutomationTakeItem(desired) } - })) + } + val container = SlottedContainer.simple(1, ::Slot, ::itemContainerUpdated).also(::addDroppableContainer) + val itemConfig = ConfigurableItemHandler(inputOutput = container) val energyConfig = ConfigurableEnergy(energy) val matterNode = object : MatterNode() { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt index b7908ae5e..3948d6d64 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/PatternStorageBlockEntity.kt @@ -1,8 +1,8 @@ package ru.dbotthepony.mc.otm.block.entity.matter import net.minecraft.core.BlockPos +import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.block.state.BlockState -import ru.dbotthepony.mc.otm.container.MatteryContainer import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.block.matter.PatternStorageBlock @@ -14,7 +14,8 @@ import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.capability.matter.* -import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.collect.filterNotNull import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.filterNotNull @@ -23,53 +24,48 @@ import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import java.util.stream.Stream -class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryDeviceBlockEntity(MBlockEntities.PATTERN_STORAGE, p_155229_, p_155230_), IPatternStorage { - +class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.PATTERN_STORAGE, p_155229_, p_155230_), IPatternStorage { val matterNode = SimpleMatterNode(patterns = this) - val container: MatteryContainer = object : MatteryContainer(::markDirtyFast, 8) { - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - if (!ItemStack.isSameItemSameComponents(new, old)) { + private inner class Slot(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override val maxStackSize: Int + get() = 1 + + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && itemStack.getCapability(MatteryCapability.PATTERN_ITEM) != null + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return false + } + + override fun notifyChanged(old: ItemStack) { + if (!ItemStack.isSameItemSameComponents(item, old)) { if (!old.isEmpty) { old.getCapability(MatteryCapability.PATTERN_ITEM)?.let { cap: IPatternStorage -> cap.patterns.forEach { matterNode.graph.onPatternRemoved(it) } } } - if (!new.isEmpty) { - new.getCapability(MatteryCapability.PATTERN_ITEM)?.let { cap: IPatternStorage -> + if (!item.isEmpty) { + item.getCapability(MatteryCapability.PATTERN_ITEM)?.let { cap: IPatternStorage -> cap.patterns.forEach { matterNode.graph.onPatternAdded(it) } } } - updateBlockstate() + val level = level + + if (level is ServerLevel) { + level.setBlock(blockPos, blockState.setValue(PatternStorageBlock.PATTERN_STORAGE_DISKS_PROPS[slot], item.getCapability(MatteryCapability.PATTERN_ITEM) != null), Block.UPDATE_CLIENTS) + } } - super.setChanged(slot, new, old) - } - - override fun getMaxStackSize(): Int = 1 - }.also(::addDroppableContainer) - - private fun updateBlockstate() { - val level = level ?: return - - var state = blockState - - for (i in 0..7) { - state = state.setValue( - PatternStorageBlock.PATTERN_STORAGE_DISKS_PROPS[i], - this.container.getItem(i).getCapability(MatteryCapability.PATTERN_ITEM) != null - ) - } - - if (state !== blockState) { - level.setBlock(blockPos, state, Block.UPDATE_CLIENTS) + super.notifyChanged(old) } } - val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(HandlerFilter.IsPattern.and(HandlerFilter.OnlyIn))) + val container = SlottedContainer.simple(2 * 4, ::Slot, ::markDirtyFast).also(::addDroppableContainer) + val itemConfig = ConfigurableItemHandler(inputOutput = container) override fun setLevel(level: Level) { super.setLevel(level) @@ -96,20 +92,12 @@ class PatternStorageBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : } override val patternCapacity: Int get() { - var stored = 0L - - for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN_ITEM) }.filterNotNull()) - stored += pattern.patternCapacity.toLong() - + val stored = container.sumOf { it.getCapability(MatteryCapability.PATTERN_ITEM)?.patternCapacity?.toLong() ?: 0L } return if (stored > Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt() } override val storedPatterns: Int get() { - var stored = 0L - - for (pattern in this.container.iterator().map { it.getCapability(MatteryCapability.PATTERN_ITEM) }.filterNotNull()) - stored += pattern.storedPatterns.toLong() - + val stored = container.sumOf { it.getCapability(MatteryCapability.PATTERN_ITEM)?.storedPatterns?.toLong() ?: 0L } return if (stored > Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt index 877a2d08a..5f76987db 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveRackBlockEntity.kt @@ -14,7 +14,8 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.storage.DriveRackMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.storage.optics.priority @@ -45,19 +46,21 @@ class DriveRackBlockEntity(blockPos: BlockPos, blockState: BlockState) : Mattery markDirtyFast() } - val container: MatteryContainer = object : MatteryContainer(::markDirtyFast, 4) { - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) + private inner class Slot(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override fun notifyChanged(old: ItemStack) { + super.notifyChanged(old) old.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let { cell.removeStorageComponent(it.priority(::insertPriority, ::extractPriority).powered(energy).flow(::mode)) } - new.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let { + item.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let { cell.addStorageComponent(it.priority(::insertPriority, ::extractPriority).powered(energy).flow(::mode)) } } - }.also(::addDroppableContainer) + } + + val container = SlottedContainer.simple(4, ::Slot, ::markDirtyFast).also(::addDroppableContainer) init { savetables.stateful(::energy, ENERGY_KEY) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt index 91b4f661b..2e01e7368 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/DriveViewerBlockEntity.kt @@ -17,7 +17,7 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter @@ -29,15 +29,16 @@ class DriveViewerBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyUpdated, MachinesConfig.DRIVE_VIEWER)) val energyConfig = ConfigurableEnergy(energy) - val container: MatteryContainer = object : MatteryContainer(::markDirtyFast, 1) { - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) + val container: EnhancedContainer.Simple = object : EnhancedContainer.Simple(1) { + override fun notifySlotChanged(slot: Int, old: ItemStack) { + super.notifySlotChanged(slot, old) + markDirtyFast() val level = level if (level is ServerLevel) { tickList.once { - val isPresent = new.getCapability(MatteryCapability.CONDENSATION_DRIVE) != null + val isPresent = get(slot).getCapability(MatteryCapability.CONDENSATION_DRIVE) != null var state = this@DriveViewerBlockEntity.blockState.setValue(DriveViewerBlock.DRIVE_PRESENT, isPresent) if (!isPresent) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt index 0d6a8f427..26d051e29 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/ItemMonitorBlockEntity.kt @@ -29,12 +29,14 @@ import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.Widgets8 import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.CombinedContainer -import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.IEnhancedCraftingContainer import ru.dbotthepony.mc.otm.container.util.slotIterator import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.collect.toList import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.mapString import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter @@ -94,42 +96,42 @@ class ItemMonitorPlayerSettings : INBTSerializable, IItemMonitorPla enum class IngredientPriority(override val component: Component, icon: Lazy, override val winding: UVWindingOrder = UVWindingOrder.NORMAL) : Setting { // Refill everything from system SYSTEM(TranslatableComponent("otm.gui.item_monitor.refill_source.system"), lazy { Widgets8.WHITE_ARROW_DOWN }, UVWindingOrder.FLIP) { - override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer<*>?, id: UUID?): Boolean { return takeOne(id, view) } }, // Refill everything from player's inventory INVENTORY(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory"), lazy { Widgets8.WHITE_ARROW_DOWN }) { - override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer<*>?, id: UUID?): Boolean { return takeOne(inventory, item) } }, // Refill everything from system, if can't refill from player's inventory SYSTEM_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.system_first"), lazy { Widgets8.ARROW_SIDEWAYS }, UVWindingOrder.FLIP) { - override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer<*>?, id: UUID?): Boolean { return takeOne(id, view) || takeOne(inventory, item) } }, // Refill everything from player's inventory, if can't refill from system INVENTORY_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory_first"), lazy { Widgets8.ARROW_SIDEWAYS }) { - override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer<*>?, id: UUID?): Boolean { return takeOne(inventory, item) || takeOne(id, view) } }, // Do not refill (?) DO_NOT(TranslatableComponent("otm.gui.item_monitor.refill_source.do_not"), lazy { Widgets8.MINUS }) { - override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean { + override fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer<*>?, id: UUID?): Boolean { return false } }; override val icon: IGUIRenderable by icon - abstract fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer?, id: UUID?): Boolean + abstract fun takeOne(item: ItemStack, view: IStorageProvider?, inventory: CombinedContainer<*>?, id: UUID?): Boolean } enum class ResultTarget(override val component: Component, icon: Lazy, override val winding: UVWindingOrder = UVWindingOrder.NORMAL) : Setting { @@ -216,17 +218,13 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte fun howMuchPlayerCrafted(ply: Player): Int = craftingAmount.getInt(ply) fun lastCraftingRecipe(ply: Player) = lastCraftingRecipe[ply] - // a lot of code is hardcoded to take CraftingContainer as it's input - // hence we are forced to work around this by providing proxy container - val craftingGrid = object : MatteryCraftingContainer(::markDirtyFast, 3, 3) { - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - markDirtyFast() + val craftingGrid = IEnhancedCraftingContainer.Wrapper(EnhancedContainer.WithListener(3 * 3) { + markDirtyFast() - if (!inProcessOfCraft) { - scanCraftingGrid(false) - } + if (!inProcessOfCraft) { + scanCraftingGrid(false) } - }.also(::addDroppableContainer) + }, 3, 3).also(::addDroppableContainer) private fun scanCraftingGrid(justCheckForRecipeChange: Boolean): Boolean { val level = level ?: return false @@ -346,8 +344,8 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte check(residue.size == craftingGrid.containerSize) { "Container and residue list sizes mismatch: ${residue.size} != ${craftingGrid.containerSize}" } - val combinedInventory = craftingPlayer.matteryPlayer?.inventoryAndExopack - val copy = craftingGrid.iterator(false).map { it.copy() }.toList() + val combinedInventory = craftingPlayer.matteryPlayer.inventoryAndExopack + val copy = craftingGrid.parent.copyToList() // удаляем по одному предмету из сетки крафта for (slot in 0 until craftingGrid.containerSize) { @@ -381,7 +379,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte remaining = poweredView?.insertStack(ItemStorageStack(remaining), false)?.toItemStack() ?: remaining } - remaining = combinedInventory?.addItem(remaining, false) ?: remaining + remaining = combinedInventory.addItem(remaining, false) if (remaining.isNotEmpty) { craftingPlayer.spawnAtLocation(remaining) @@ -425,7 +423,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte } }) - nbt["crafting_grid"] = craftingGrid.serializeNBT(registry) + nbt["crafting_grid"] = craftingGrid.parent.serializeNBT(registry) } override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) { @@ -438,7 +436,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte check(this.settings.put(UUID.fromString(key), ItemMonitorPlayerSettings().also { it.deserializeNBT(registry, settings.getCompound(key)) }) == null) } - craftingGrid.deserializeNBT(registry, nbt["crafting_grid"]) + nbt.map("crafting_grid", craftingGrid.parent::deserializeNBT, registry) } fun getSettings(ply: ServerPlayer): ItemMonitorPlayerSettings { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt index 9430419f0..056d2e832 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt @@ -23,7 +23,7 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.* import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.isPositive @@ -123,7 +123,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter }) } - var filter = ItemFilter(MAX_FILTERS) + var filter = ItemFilterSet.EMPTY set(value) { field = value component?.scan() @@ -131,7 +131,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } init { - savetablesConfig.codec(::filter, ItemFilter.CODEC, FILTER_KEY, Supplier { ItemFilter(MAX_FILTERS) }) + savetablesConfig.codec(::filter, ItemFilterSet.CODEC, FILTER_KEY, Supplier { ItemFilterSet.EMPTY }) } override fun setLevel(level: Level) { @@ -348,7 +348,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } fun scan(slot: Int) { - val current = parent[slot].let { if (it.isEmpty || !filter.match(it)) null else it } + val current = parent[slot].let { if (it.isEmpty || !filter.test(it)) null else it } val last = slot2itemStack[slot] if (current == null && last != null) { @@ -374,7 +374,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } override fun insertStack(stack: ItemStorageStack, simulate: Boolean): ItemStorageStack { - if (redstoneControl.isBlockedByRedstone || energy.batteryLevel.isZero || !filter.match(stack.toItemStack()) || !mode.input) + if (redstoneControl.isBlockedByRedstone || energy.batteryLevel.isZero || !filter.test(stack.toItemStack()) || !mode.input) return stack val required = StorageStack.ITEMS.energyPerInsert(stack) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt index 17aa2e953..d6573d11b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt @@ -23,7 +23,7 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.config.EnergyBalanceValues import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.math.RelativeSide @@ -98,7 +98,7 @@ abstract class AbstractStorageImportExport( protected val target = CapabilityCache(RelativeSide.FRONT, Capabilities.ItemHandler.BLOCK) - var filter: ItemFilter = ItemFilter(MAX_FILTERS) + var filter: ItemFilterSet = ItemFilterSet.EMPTY set(value) { if (value != field) { field = value @@ -112,7 +112,7 @@ abstract class AbstractStorageImportExport( } init { - savetablesConfig.codec(::filter, ItemFilter.CODEC, FILTER_KEY, Supplier { ItemFilter(MAX_FILTERS) }) + savetablesConfig.codec(::filter, ItemFilterSet.CODEC, FILTER_KEY, Supplier { ItemFilterSet.EMPTY }) } companion object { @@ -168,7 +168,7 @@ class StorageImporterBlockEntity( } override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { - if (redstoneControl.isBlockedByRedstone || !filter.match(stack)) + if (redstoneControl.isBlockedByRedstone || !filter.test(stack)) return stack return acceptItem(stack, simulate) @@ -183,7 +183,7 @@ class StorageImporterBlockEntity( } override fun isItemValid(slot: Int, stack: ItemStack): Boolean { - return filter.match(stack) + return filter.test(stack) } override fun tick() { @@ -205,7 +205,7 @@ class StorageImporterBlockEntity( val extracted = target.extractItem(lastSlot, MAX_MOVE_PER_OPERATION, true) - if (extracted.isEmpty || !filter.match(extracted)) { + if (extracted.isEmpty || !filter.test(extracted)) { lastSlot++ } else { val leftover = acceptItem(extracted, true) @@ -244,7 +244,7 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : } override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider) { - if (filter.match(stack.toItemStack())) { + if (filter.test(stack.toItemStack())) { relevantTuples.add(id) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt index 4ca9c7317..e92851a9f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AbstractPoweredFurnaceBlockEntity.kt @@ -16,6 +16,7 @@ import net.minecraft.world.item.crafting.SmokingRecipe import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState import net.neoforged.neoforge.capabilities.Capabilities +import net.neoforged.neoforge.event.AddReloadListenerEvent import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage @@ -29,33 +30,60 @@ import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.config.WorkerBalanceValues import ru.dbotthepony.mc.otm.container.CombinedContainer -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.balance -import ru.dbotthepony.mc.otm.core.collect.filter -import ru.dbotthepony.mc.otm.core.collect.maybe +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.core.SimpleCache import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.otmRandom +import ru.dbotthepony.mc.otm.container.ItemStackKey +import ru.dbotthepony.mc.otm.container.asKey import ru.dbotthepony.mc.otm.menu.tech.PoweredFurnaceMenu import ru.dbotthepony.mc.otm.recipe.MatteryCookingRecipe import ru.dbotthepony.mc.otm.recipe.MicrowaveRecipe import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.registry.game.MRecipes +import java.time.Duration sealed class AbstractPoweredFurnaceBlockEntity

( type: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState, - val recipeType: RecipeType

, - val secondaryRecipeType: RecipeType?, - val config: WorkerBalanceValues, maxJobs: Int = 2 ) : MatteryWorkerBlockEntity(type, blockPos, blockState, ItemJob.CODEC, maxJobs) { + abstract val recipeType: RecipeType

+ abstract val secondaryRecipeType: RecipeType? + abstract val config: WorkerBalanceValues + final override val upgrades = makeUpgrades(2, UpgradeType.BASIC_PROCESSING) final override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(config))) - val inputs = MatteryContainer(this::itemContainerUpdated, maxJobs) - val outputs = MatteryContainer(this::itemContainerUpdated, maxJobs) + private inner class InputSlot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + if (!super.canAutomationPlaceItem(itemStack)) + return false + + val level = level ?: return true + + return acceptableItems.get(itemStack.asKey()) { + val input = SingleRecipeInput(itemStack) + val secondaryRecipeType = secondaryRecipeType + + if (secondaryRecipeType != null && level.recipeManager.byType(secondaryRecipeType).any { it.value.matches(input, level) }) + return@get true + + return@get level.recipeManager.byType(recipeType).any { it.value.matches(input, level) } + } + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return false + } + } + + val inputs = SlottedContainer.simple(maxJobs, ::InputSlot, ::itemContainerUpdated) + val outputs = SlottedContainer.simple(maxJobs, AutomationFilters.ONLY_OUT.simpleProvider, ::itemContainerUpdated) init { addDroppableContainer(inputs) @@ -76,9 +104,9 @@ sealed class AbstractPoweredFurnaceBlockEntity

(Duration.ofMinutes(1)) + + internal fun onReload(event: AddReloadListenerEvent) { + acceptableItems.invalidateAll() + } + } } -class PoweredFurnaceBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractPoweredFurnaceBlockEntity( - MBlockEntities.POWERED_FURNACE, blockPos, blockState, RecipeType.SMELTING, null, MachinesConfig.POWERED_FURNACE) { +class PoweredFurnaceBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractPoweredFurnaceBlockEntity(MBlockEntities.POWERED_FURNACE, blockPos, blockState) { + override val recipeType: RecipeType + get() = RecipeType.SMELTING + override val secondaryRecipeType: RecipeType? + get() = null + override val config: WorkerBalanceValues + get() = MachinesConfig.POWERED_FURNACE + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return PoweredFurnaceMenu.furnace(containerID, inventory, this) } } -class PoweredBlastFurnaceBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractPoweredFurnaceBlockEntity( - MBlockEntities.POWERED_BLAST_FURNACE, blockPos, blockState, RecipeType.BLASTING, null, MachinesConfig.POWERED_FURNACE) { +class PoweredBlastFurnaceBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractPoweredFurnaceBlockEntity(MBlockEntities.POWERED_BLAST_FURNACE, blockPos, blockState) { + override val recipeType: RecipeType + get() = RecipeType.BLASTING + override val secondaryRecipeType: RecipeType? + get() = null + override val config: WorkerBalanceValues + get() = MachinesConfig.POWERED_FURNACE + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return PoweredFurnaceMenu.blasting(containerID, inventory, this) } } -class PoweredSmokerBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractPoweredFurnaceBlockEntity( - MBlockEntities.POWERED_SMOKER, blockPos, blockState, RecipeType.SMOKING, MRecipes.MICROWAVE, MachinesConfig.POWERED_FURNACE) { +class PoweredSmokerBlockEntity(blockPos: BlockPos, blockState: BlockState) : AbstractPoweredFurnaceBlockEntity(MBlockEntities.POWERED_SMOKER, blockPos, blockState) { + override val recipeType: RecipeType + get() = RecipeType.SMOKING + override val secondaryRecipeType: RecipeType + get() = MRecipes.MICROWAVE + override val config: WorkerBalanceValues + get() = MachinesConfig.POWERED_FURNACE + override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return PoweredFurnaceMenu.smoking(containerID, inventory, this) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt index cd5940b7b..882eea52d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/BatteryBankBlockEntity.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.block.entity.tech +import it.unimi.dsi.fastutil.ints.IntArrayList import net.minecraft.core.BlockPos import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player @@ -12,6 +13,7 @@ import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.util.value import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity 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.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage @@ -19,9 +21,12 @@ import ru.dbotthepony.mc.otm.capability.energyStoredMattery import ru.dbotthepony.mc.otm.capability.matteryEnergy import ru.dbotthepony.mc.otm.capability.maxEnergyStoredMattery import ru.dbotthepony.mc.otm.capability.transcieveEnergy -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotRange +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.otmRandom import ru.dbotthepony.mc.otm.core.shuffle @@ -33,31 +38,73 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte var gaugeLevel by syncher.float() private set - // 6 на 2 - val container: MatteryContainer = object : MatteryContainer(::setChanged, CAPACITY) { - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) - batteryStatus[slot].value = new.getCapability(Capabilities.EnergyStorage.ITEM) != null - gaugeLevel = batteryLevel.percentage(maxBatteryLevel) + private var containerSlotIndices = IntArray(0) + + private inner class Slot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && itemStack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { it.canExtract() && it.extractEnergy(Int.MAX_VALUE, true) > 0 } == true } - override fun getMaxStackSize(): Int = 1 - }.also(::addDroppableContainer) + override fun canAutomationTakeItem(desired: Int): Boolean { + return super.canAutomationTakeItem(desired) && item.getCapability(Capabilities.EnergyStorage.ITEM)?.let { !it.canExtract() || it.extractEnergy(Int.MAX_VALUE, true) <= 0 } != false + } - val batteryStatus = immutableList(CAPACITY) { - syncher.boolean(false) + override fun notifyChanged(old: ItemStack) { + super.notifyChanged(old) + + batteryStatus[slot].value = item.getCapability(Capabilities.EnergyStorage.ITEM) != null + updateGaugeLevel() + } + + override val maxStackSize: Int + get() = 1 } - val itemConfig = ConfigurableItemHandler(inputOutput = container.handler(HandlerFilter.Dischargeable)) + private fun containerUpdated() { + markDirtyFast() + + val newSlots = IntArrayList() + + for (i in 0 until container.containerSize) { + val stack = container[i] + + if (stack.isNotEmpty && stack.energy != null) + newSlots.add(i) + } + + containerSlotIndices = newSlots.toIntArray() + } + + // 6 на 2 + val container = SlottedContainer.simple(CAPACITY, ::Slot, ::containerUpdated).also(::addDroppableContainer) + val batteryStatus = immutableList(CAPACITY) { syncher.boolean(false) } + + val itemConfig = ConfigurableItemHandler(inputOutput = container) init { savetables.stateful(::container, INVENTORY_KEY) } - private val containerSlotIndices = IntArray(CAPACITY) { it } + private fun updateGaugeLevel() { + var stored = Decimal.ZERO + var maxStored = Decimal.ZERO + + for (stack in container) { + val cap = stack.energy ?: continue + stored += cap.energyStoredMattery + maxStored += cap.maxEnergyStoredMattery + } + + gaugeLevel = stored.percentage(maxStored) + } + + override fun tick() { + super.tick() + updateGaugeLevel() + } private fun distributeEnergy(isReceiving: Boolean, howMuch: Decimal, simulate: Boolean): Decimal { - if (!howMuch.isPositive) + if (!howMuch.isPositive || containerSlotIndices.isEmpty()) return Decimal.ZERO containerSlotIndices.shuffle(level!!.otmRandom) @@ -84,7 +131,6 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte if (!simulate && !summ.isZero) { markDirtyFast() - gaugeLevel = batteryLevel.percentage(maxBatteryLevel) } return summ @@ -108,13 +154,9 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte get() { var result = Decimal.ZERO - for (i in 0 until container.containerSize) { - val stack = container.getItem(i) - - if (!stack.isEmpty) { - stack.energy?.let { - result += it.energyStoredMattery - } + for (stack in container) { + stack.energy?.let { + result += it.energyStoredMattery } } @@ -128,13 +170,9 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte get() { var result = Decimal.ZERO - for (i in 0 until container.containerSize) { - val stack = container.getItem(i) - - if (!stack.isEmpty) { - stack.energy?.let { - result += it.maxEnergyStoredMattery - } + for (stack in container) { + stack.energy?.let { + result += it.maxEnergyStoredMattery } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt index 1c9214737..f37b9ff4d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ChemicalGeneratorBlockEntity.kt @@ -12,8 +12,8 @@ import ru.dbotthepony.mc.otm.capability.* import ru.dbotthepony.mc.otm.capability.energy.GeneratorEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.core.math.Decimal @@ -23,20 +23,16 @@ class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDe return ChemicalGeneratorMenu(containerID, inventory, this) } - val batteryContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) - val residueContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) - val fuelContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) - - val batteryItemHandler = batteryContainer.handler(HandlerFilter.Chargeable) - val residueItemHandler = residueContainer.handler(HandlerFilter.OnlyOut) - val fuelItemHandler = fuelContainer.handler(HandlerFilter.ChemicalFuel) + val batteryContainer = SlottedContainer.simple(1, AutomationFilters.DISCHARGABLE.filteredProvider, ::markDirtyFast).also(::addDroppableContainer) + val residueContainer = SlottedContainer.simple(1, AutomationFilters.ONLY_OUT.simpleProvider, ::markDirtyFast).also(::addDroppableContainer) + val fuelContainer = SlottedContainer.simple(1, AutomationFilters.CHEMICAL_FUEL.filteredProvider, ::markDirtyFast).also(::addDroppableContainer) val energy = ProfiledEnergyStorage(GeneratorEnergyStorage(::markDirtyFast, MachinesConfig.ChemicalGenerator.VALUES::energyCapacity, MachinesConfig.ChemicalGenerator.VALUES::energyThroughput)) val itemConfig = ConfigurableItemHandler( - input = fuelItemHandler, - output = residueItemHandler, - battery = batteryItemHandler, + input = fuelContainer, + output = residueContainer, + battery = batteryContainer, backDefault = ItemHandlerMode.BATTERY) val energyConfig = ConfigurableEnergy(energy) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt index a54b1a092..fe27531b7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt @@ -11,8 +11,8 @@ 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.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities @@ -22,8 +22,8 @@ class CobblerBlockEntity(blockPos: BlockPos, blockState: BlockState) return CobblerMenu(containerID, inventory, this) } - val container = MatteryContainer(this::itemContainerUpdated, CONTAINER_SIZE).also(::addDroppableContainer) - val itemConfig = ConfigurableItemHandler(output = container.handler(HandlerFilter.OnlyOut)) + val container = SlottedContainer.simple(CONTAINER_SIZE, AutomationFilters.ONLY_OUT.simpleProvider, ::itemContainerUpdated).also(::addDroppableContainer) + val itemConfig = ConfigurableItemHandler(output = container) init { savetables.stateful(::container, INVENTORY_KEY) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt index 80b12d46f..2b1bb16e0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt @@ -22,7 +22,7 @@ import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart import ru.dbotthepony.mc.otm.core.math.BlockRotation import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.RelativeSide -import ru.dbotthepony.mc.otm.core.math.getDecimal +import ru.dbotthepony.mc.otm.core.nbt.getDecimal import ru.dbotthepony.mc.otm.core.nbt.mapPresent import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.util.countingLazy @@ -31,6 +31,7 @@ import ru.dbotthepony.mc.otm.registry.game.MBlockEntities class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ENERGY_COUNTER, p_155229_, p_155230_) { var passed by syncher.decimal() + var pullEnergyFromInput by syncher.boolean() override val blockRotation: BlockRotation by countingLazy(blockStateChangesCounter) { BlockRotation.of(blockState[EnergyCounterBlock.INPUT_DIRECTION]) @@ -86,10 +87,8 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat savetables.stateful(::history1h) savetables.stateful(::history6h) savetables.stateful(::history24h) - } - override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) { - super.saveLevel(nbt, registry) + savetablesConfig.bool(::pullEnergyFromInput) } override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) { @@ -267,6 +266,14 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat override fun tick() { super.tick() + if (pullEnergyFromInput) { + val input = inputCapability + val output = outputCapability + + if (input != null && output != null) + thisTick += moveEnergy(source = input, destination = output, simulate = false) + } + lastTick = thisTick charts.forEach { it.add(thisTick) } thisTick = Decimal.ZERO diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt index 4e6dfd6ca..733cdb3ea 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyHatchBlockEntity.kt @@ -16,8 +16,8 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.moveEnergy import ru.dbotthepony.mc.otm.config.EnergyBalanceValues import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag import ru.dbotthepony.mc.otm.menu.tech.EnergyHatchMenu @@ -32,13 +32,12 @@ class EnergyHatchBlockEntity( ) : MatteryDeviceBlockEntity(type, blockPos, blockState) { val energy = ProfiledEnergyStorage(BlockEnergyStorageImpl(this::markDirtyFast, FlowDirection.input(isInput), capacity)) - val container = object : MatteryContainer(::markDirtyFast, CAPACITY) { - override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { - return 1 - } - }.also(::addDroppableContainer) + val container = SlottedContainer.simple( + CAPACITY, + if (isInput) AutomationFilters.DISCHARGABLE.limitedFilteredProvider else AutomationFilters.CHARGEABLE.limitedFilteredProvider, + ::markDirtyFast + ).also(::addDroppableContainer) - val itemHandler = container.handler(if (isInput) HandlerFilter.Dischargeable else HandlerFilter.Chargeable) private val neighbours = ArrayList>() init { @@ -47,7 +46,7 @@ class EnergyHatchBlockEntity( // it would cause a lot of frustration if hatches accept stuff only though one face exposeEnergyGlobally(energy) - exposeGlobally(Capabilities.ItemHandler.BLOCK, itemHandler) + exposeGlobally(Capabilities.ItemHandler.BLOCK, container) if (!isInput) { for (side in RelativeSide.entries) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyInterfaceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyInterfaceBlockEntity.kt index 8a74c8afd..8b8185fdd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyInterfaceBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyInterfaceBlockEntity.kt @@ -18,8 +18,8 @@ import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.energy.CombinedProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.moveEnergy -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag import ru.dbotthepony.mc.otm.core.multiblock.IMultiblockAccess @@ -56,13 +56,11 @@ class EnergyInterfaceBlockEntity( targets.invalidate() } - val container = object : MatteryContainer(::markDirtyFast, CAPACITY) { - override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { - return 1 - } - }.also(::addDroppableContainer) + val container = SlottedContainer.simple( + CAPACITY, + if (isInput) AutomationFilters.DISCHARGABLE.limitedFilteredProvider else AutomationFilters.CHARGEABLE.limitedFilteredProvider, + ::markDirtyFast).also(::addDroppableContainer) - val itemHandler = container.handler(if (isInput) HandlerFilter.Dischargeable else HandlerFilter.Chargeable) private val neighbours = ArrayList>() override fun setRemoved() { @@ -75,7 +73,7 @@ class EnergyInterfaceBlockEntity( // it would cause a lot of frustration if hatches accept stuff only though one face exposeEnergyGlobally(energy) - exposeGlobally(Capabilities.ItemHandler.BLOCK, itemHandler) + exposeGlobally(Capabilities.ItemHandler.BLOCK, container) if (!isInput) { for (side in RelativeSide.entries) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt index 4cf96e086..0e60cf388 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyServoBlockEntity.kt @@ -14,16 +14,19 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energyStoredMattery import ru.dbotthepony.mc.otm.capability.extractEnergy import ru.dbotthepony.mc.otm.capability.maxEnergyStoredMattery +import ru.dbotthepony.mc.otm.capability.moveEnergy import ru.dbotthepony.mc.otm.capability.receiveEnergy -import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.container.slotted.and import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.menu.tech.EnergyServoMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities class EnergyServoBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ENERGY_SERVO, blockPos, blockState) { - val discharge = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) - val charge = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + val discharge = SlottedContainer.simple(1, AutomationFilters.DISCHARGABLE.filteredProvider, ::markDirtyFast).also(::addDroppableContainer) + val charge = SlottedContainer.simple(1, AutomationFilters.CHARGEABLE.filteredProvider, ::markDirtyFast).also(::addDroppableContainer) val energy: ProfiledEnergyStorage = ProfiledEnergyStorage(object : IMatteryEnergyStorage { override val energyFlow: FlowDirection get() { @@ -61,11 +64,7 @@ class EnergyServoBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte }) val energyConfig = ConfigurableEnergy(energy, possibleModes = FlowDirection.BI_DIRECTIONAL) - - val itemConfig = ConfigurableItemHandler( - input = charge.handler(HandlerFilter.OnlyIn.and(HandlerFilter.Chargeable)), - output = discharge.handler(HandlerFilter.OnlyOut.and(HandlerFilter.Dischargeable)) - ) + val itemConfig = ConfigurableItemHandler(input = charge, output = discharge) init { savetables.stateful(::charge) @@ -89,19 +88,7 @@ class EnergyServoBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte val chargeEnergy = charge.energy ?: return val dischargeEnergy = discharge.energy ?: return - val extracted = dischargeEnergy.extractEnergy(Decimal.LONG_MAX_VALUE, true) - - if (extracted.isPositive) { - val received = chargeEnergy.receiveEnergy(extracted, true) - - if (received.isPositive) { - val extracted2 = dischargeEnergy.extractEnergy(received, false) - - if (extracted2 == received) { - chargeEnergy.receiveEnergy(dischargeEnergy.extractEnergy(received, false), false) - } - } - } + moveEnergy(source = dischargeEnergy, destination = chargeEnergy, amount = Decimal.LONG_MAX_VALUE, simulate = false) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt index 9695e8dea..f30aece84 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EssenceStorageBlockEntity.kt @@ -23,8 +23,10 @@ import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.tech.EssenceStorageBlock import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.getEntitiesInEllipsoid import ru.dbotthepony.mc.otm.core.lookupOrThrow import ru.dbotthepony.mc.otm.core.math.Vector @@ -36,6 +38,7 @@ import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.registry.game.MDataComponentTypes import ru.dbotthepony.mc.otm.registry.game.MFluids +import kotlin.math.max class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ESSENCE_STORAGE, blockPos, blockState), IFluidHandler { var experienceStored = 0L @@ -45,9 +48,29 @@ class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma markDirtyFast() } - val capsuleContainer = MatteryContainer(::markDirtyFast, 1) - val servoContainer = MatteryContainer(::markDirtyFast, 1) - val mendingContainer = MatteryContainer(::markDirtyFast, 1).also(::addDroppableContainer) + private class CapsuleSlot(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && itemStack.item is EssenceCapsuleItem + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return false + } + } + + private inner class MendingSlot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && itemStack.isDamaged && itemStack.getEnchantmentLevel(mending!!) > 0 + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return super.canAutomationTakeItem(desired) && (!item.isDamaged || experienceStored <= 0) + } + } + + val capsuleContainer = SlottedContainer.simple(1, ::CapsuleSlot, ::markDirtyFast) + val servoContainer = EnhancedContainer.WithListener(1, ::markDirtyFast) + val mendingContainer = SlottedContainer.simple(1, ::MendingSlot, ::markDirtyFast).also(::addDroppableContainer) private var mending: Holder? = null @@ -63,22 +86,7 @@ class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma } val itemConfig = ConfigurableItemHandler( - inputOutput = CombinedItemHandler( - capsuleContainer.handler(HandlerFilter.OnlyIn.and(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.item is EssenceCapsuleItem - } - })), - mendingContainer.handler(object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.isDamaged && stack.getEnchantmentLevel(mending!!) > 0 - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return !stack.isDamaged || experienceStored <= 0 - } - }) - ) + inputOutput = CombinedItemHandler(capsuleContainer, mendingContainer) ) override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) { @@ -160,7 +168,7 @@ class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma val capsule = capsuleContainer[0] if (!capsule.isEmpty && capsule.has(MDataComponentTypes.EXPERIENCE)) { - experienceStored += capsule.get(MDataComponentTypes.EXPERIENCE)!! * capsule.count + experienceStored += max(capsule.get(MDataComponentTypes.EXPERIENCE)!! * capsule.count, 0L) capsuleContainer.clearContent() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt index 8ebffbb02..848bb0138 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/ItemHatchBlockEntity.kt @@ -9,8 +9,8 @@ import net.minecraft.world.level.block.state.BlockState import net.neoforged.neoforge.capabilities.Capabilities import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag import ru.dbotthepony.mc.otm.menu.tech.ItemHatchMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities @@ -21,12 +21,11 @@ class ItemHatchBlockEntity( blockPos: BlockPos, blockState: BlockState ) : MatteryDeviceBlockEntity(type, blockPos, blockState) { - val container = MatteryContainer(this::markDirtyFast, CAPACITY).also(::addDroppableContainer) - val itemHandler = container.handler(if (isInput) HandlerFilter.OnlyIn else HandlerFilter.OnlyOut) + val container = SlottedContainer.simple(CAPACITY, if (isInput) AutomationFilters.ONLY_IN.filteredProvider else AutomationFilters.ONLY_OUT.filteredProvider, this::markDirtyFast).also(::addDroppableContainer) init { savetables.stateful(::container, INVENTORY_KEY) - exposeGlobally(Capabilities.ItemHandler.BLOCK, itemHandler) + exposeGlobally(Capabilities.ItemHandler.BLOCK, container) } override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt index f03a7aaf7..032d2685b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/MatterHatchBlockEntity.kt @@ -15,8 +15,8 @@ import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage import ru.dbotthepony.mc.otm.capability.moveMatter import ru.dbotthepony.mc.otm.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.HandlerFilter -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag import ru.dbotthepony.mc.otm.menu.tech.MatterHatchMenu @@ -28,26 +28,15 @@ class MatterHatchBlockEntity( blockPos: BlockPos, blockState: BlockState ) : MatteryDeviceBlockEntity(type, blockPos, blockState) { - val container = object : MatteryContainer(::markDirtyFast, CAPACITY) { - override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { - return 1 - } - }.also(::addDroppableContainer) - + val container = SlottedContainer.simple(CAPACITY, if (isInput) AutomationFilters.MATTER_PROVIDERS.limitedFilteredProvider else AutomationFilters.MATTER_CONSUMERS.limitedFilteredProvider, ::markDirtyFast).also(::addDroppableContainer) val matter = ProfiledMatterStorage(MatterStorageImpl(this::markDirtyFast, FlowDirection.input(isInput), MachinesConfig::MATTER_HATCH)) - val itemHandler = if (isInput) { - container.handler(HandlerFilter.MatterProviders) - } else { - container.handler(HandlerFilter.MatterConsumers) - } - init { savetables.stateful(::container, INVENTORY_KEY) savetables.stateful(::matter, MATTER_STORAGE_KEY) // it would cause a lot of frustration if hatches accept stuff only though one face - exposeGlobally(Capabilities.ItemHandler.BLOCK, itemHandler) + exposeGlobally(Capabilities.ItemHandler.BLOCK, container) exposeGlobally(MatteryCapability.MATTER_BLOCK, matter) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt index bc8c6e1aa..3f1c9ca45 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt @@ -5,8 +5,11 @@ 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.item.crafting.SingleRecipeInput import net.minecraft.world.level.block.state.BlockState import net.neoforged.neoforge.capabilities.Capabilities +import net.neoforged.neoforge.event.AddReloadListenerEvent import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus @@ -16,15 +19,18 @@ 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.config.MachinesConfig -import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.balance -import ru.dbotthepony.mc.otm.core.collect.filter -import ru.dbotthepony.mc.otm.core.collect.maybe +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.FilteredContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.core.SimpleCache import ru.dbotthepony.mc.otm.core.otmRandom +import ru.dbotthepony.mc.otm.container.ItemStackKey +import ru.dbotthepony.mc.otm.container.asKey import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.registry.game.MRecipes +import java.time.Duration class PlatePressBlockEntity( blockPos: BlockPos, @@ -33,14 +39,36 @@ class PlatePressBlockEntity( ) : MatteryWorkerBlockEntity(if (isTwin) MBlockEntities.TWIN_PLATE_PRESS else MBlockEntities.PLATE_PRESS, blockPos, blockState, ItemJob.CODEC, if (isTwin) 2 else 1) { override val upgrades = makeUpgrades(if (isTwin) 4 else 3, UpgradeType.BASIC_PROCESSING) override val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(MachinesConfig.PLATE_PRESS))) - val inputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer) - val outputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer) + + private inner class InputSlot(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + if (!super.canAutomationPlaceItem(itemStack)) + return false + + val level = level ?: return true + val input = SingleRecipeInput(itemStack) + + return cache.get(itemStack.asKey()) { + return@get level.recipeManager + .byType(MRecipes.PLATE_PRESS) + .any { it.value.matches(input, level) } + } + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return false + } + } + + val inputContainer = SlottedContainer.simple(if (isTwin) 2 else 1, ::InputSlot, this::itemContainerUpdated).also(::addDroppableContainer) + val outputContainer = SlottedContainer.simple(if (isTwin) 2 else 1, AutomationFilters.ONLY_OUT.simpleProvider, this::itemContainerUpdated).also(::addDroppableContainer) val experience = ExperienceStorage(MachinesConfig.PLATE_PRESS::maxExperienceStored).also(::addNeighbourListener) val energyConfig = ConfigurableEnergy(energy) + val itemConfig = ConfigurableItemHandler( - input = inputContainer.handler(HandlerFilter.OnlyIn), - output = outputContainer.handler(HandlerFilter.OnlyOut), + input = inputContainer, + output = outputContainer, ) init { @@ -76,9 +104,7 @@ class PlatePressBlockEntity( val recipe = level.recipeManager .byType(MRecipes.PLATE_PRESS) - .iterator() - .filter { it.value.matches(inputContainer, id) } - .maybe()?.value ?: return JobContainer.noItem() + .firstOrNull { it.value.matches(inputContainer, id) }?.value ?: return JobContainer.noItem() val toProcess = inputContainer[id].count.coerceAtMost(1 + upgrades.processingItems) @@ -100,4 +126,12 @@ class PlatePressBlockEntity( super.tick() } + + companion object { + private val cache = SimpleCache(Duration.ofMinutes(1L)) + + internal fun onReload(event: AddReloadListenerEvent) { + cache.invalidateAll() + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt index c6b6da26d..644e715c1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt @@ -146,13 +146,13 @@ val ItemStack.matteryEnergy: IMatteryEnergyStorage? get() { } fun Player.items(includeCosmetics: Boolean = true): Iterator { - val matteryPlayer = matteryPlayer ?: return emptyIterator() + val matteryPlayer = matteryPlayer val iterators = ArrayList>() - iterators.add(matteryPlayer.wrappedInventory.slotIterator().filter { !it.isForbiddenForAutomation }.map { it.item }) + iterators.add(matteryPlayer.wrappedInventory.slotIterator().filter { !it.filter.denyAll }.map { it.item }) if (matteryPlayer.hasExopack) { - iterators.add(matteryPlayer.exopackContainer.slotIterator().filter { !it.isForbiddenForAutomation }.map { it.item }) + iterators.add(matteryPlayer.exopackContainer.slotIterator().filter { !it.filter.denyAll }.map { it.item }) iterators.add(matteryPlayer.exopackEnergy.parent.iterator()) iterators.add(matteryPlayer.exopackChargeSlots.iterator()) } @@ -185,10 +185,8 @@ fun Player.awareItemsStream(includeCosmetics: Boolean = false): Stream>() streams.add(inventory.awareStream()) - matteryPlayer?.let { - if (it.hasExopack) { - streams.add(it.exopackContainer.awareStream()) - } + if (matteryPlayer.hasExopack) { + streams.add(matteryPlayer.exopackContainer.awareStream()) } if (isCuriosLoaded) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt index d00b2fb32..38e798d47 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/FlowDirection.kt @@ -136,7 +136,7 @@ enum class FlowDirection(val input: Boolean, val output: Boolean, val translatio */ @JvmStatic fun input(flag: Boolean): FlowDirection { - return of(flag, !flag) + return if (flag) INPUT else OUTPUT } /** diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IQuickStackContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IQuickStackContainer.kt new file mode 100644 index 000000000..cbf7209cc --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/IQuickStackContainer.kt @@ -0,0 +1,14 @@ +package ru.dbotthepony.mc.otm.capability + +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.inventory.Slot + +interface IQuickStackContainer { + fun getSlotsFor(player: ServerPlayer): Collection + + class Simple(private val slots: Collection) : IQuickStackContainer { + override fun getSlotsFor(player: ServerPlayer): Collection { + return slots + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt index 256a66c80..b24020479 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BatteryBackedEnergyStorage.kt @@ -15,7 +15,7 @@ import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.extractEnergy import ru.dbotthepony.mc.otm.capability.receiveEnergy import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.getDecimal +import ru.dbotthepony.mc.otm.core.nbt.getDecimal import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.registry.StatNames import ru.dbotthepony.mc.otm.triggers.AndroidBatteryTrigger diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt index 83c426d0f..1cd762f2e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/item/CombinedItemHandler.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.capability.item import com.google.common.collect.ImmutableList import net.minecraft.world.item.ItemStack import net.neoforged.neoforge.items.IItemHandler -import ru.dbotthepony.mc.otm.container.ContainerHandler +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import java.util.stream.Stream class CombinedItemHandler(val handlers: ImmutableList) : IItemHandler { @@ -11,7 +11,7 @@ class CombinedItemHandler(val handlers: ImmutableList) : IItemHand constructor(handlers: Collection) : this(ImmutableList.copyOf(handlers)) constructor(vararg handlers: IItemHandler) : this(ImmutableList.copyOf(handlers)) - private val needsChecking = handlers.any { it !is ContainerHandler } + private val needsChecking = handlers.any { it !is SlottedContainer } private val lastSizes = IntArray(this.handlers.size) private var totalSize = 0 private val mappings = ArrayList() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt index d02636fa7..5732aeeab 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExopackInventoryScreen.kt @@ -25,8 +25,11 @@ import ru.dbotthepony.mc.otm.client.setMousePos import ru.dbotthepony.mc.otm.client.shouldOpenVanillaInventory import ru.dbotthepony.mc.otm.core.math.integerDivisionDown import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu +import ru.dbotthepony.mc.otm.menu.QuickMoveInput import ru.dbotthepony.mc.otm.network.ExopackMenuOpen +import ru.dbotthepony.mc.otm.network.QuickStackPacket import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak +import java.util.function.IntConsumer @MouseTweaksDisableWheelTweak class ExopackInventoryScreen(menu: ExopackInventoryMenu) : MatteryScreen(menu, TranslatableComponent("otm.gui.exopack")) { @@ -281,7 +284,9 @@ class ExopackInventoryScreen(menu: ExopackInventoryMenu) : MatteryScreen(menu: T, inventory: Inventory, tit override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { return false } - - override var slotFilter: Item? by slot.filter!! } } @@ -425,7 +422,7 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit matter: LevelGaugeWidget? = null, profiledMatter: ProfiledLevelGaugeWidget<*>? = null, patterns: LevelGaugeWidget? = null, - batterySlot: MatterySlot? = null, + batterySlot: MatteryMenuSlot? = null, ) { var bars = 0 if (energy != null) bars++ diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt index 26bb90d13..557704aa8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/CargoCrateScreen.kt @@ -2,11 +2,13 @@ package ru.dbotthepony.mc.otm.client.screen.decorative import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel +import ru.dbotthepony.mc.otm.menu.QuickMoveInput import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { @@ -20,12 +22,19 @@ class CargoCrateScreen(menu: CargoCrateMenu, inventory: Inventory, title: Compon val grid = GridPanel(this, frame, 8f, 18f, 9f * 18f, 6f * 18f, 9, 6) for (slot in menu.storageSlots) - UserFilteredSlotPanel.of(this, grid, slot) + UserFilteredSlotPanel(this, grid, slot) val controls = DeviceControls(this, frame) - controls.sortingButtons(menu.sort) + val leftControls = DeviceControls(this, frame) + leftControls.dockOnLeft = true + leftControls.quickMoveButtons(menu.quickMoveFromStorage) + + val leftInventoryControls = DeviceControls(this, inventoryFrame!!) + leftInventoryControls.dockOnLeft = true + leftInventoryControls.quickMoveButtons(menu.quickMoveToStorage, menu.quickMoveFromStorage) + return frame } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt index 1027ce7f7..058b7a3a3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/decorative/FluidTankScreen.kt @@ -12,6 +12,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls import ru.dbotthepony.mc.otm.client.screen.panels.makeCuriosPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.SpritePanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.FluidGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel import ru.dbotthepony.mc.otm.menu.decorative.FluidTankMenu @@ -25,8 +26,8 @@ class FluidTankScreen(menu: FluidTankMenu, inventory: Inventory, title: Componen val s = SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND, x = 30f, y = 30f) SpritePanel(this, frame, ProgressGaugePanel.GAUGE_BACKGROUND, x = 30f, y = 55f, winding = UVWindingOrder.FLOP) - SlotPanel(this, frame, menu.fillInput, x = 30f + s.width + 4f, y = 28f) - SlotPanel(this, frame, menu.drainInput, x = 30f + s.width + 4f, y = 53f) + UserFilteredSlotPanel(this, frame, menu.fillInput, x = 30f + s.width + 4f, y = 28f) + UserFilteredSlotPanel(this, frame, menu.drainInput, x = 30f + s.width + 4f, y = 53f) SlotPanel(this, frame, menu.output, x = 30f + s.width + 4f + 20f, y = 53f) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterDecomposerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterDecomposerScreen.kt index 659f914bd..6ae193013 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterDecomposerScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterDecomposerScreen.kt @@ -8,6 +8,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.ProfiledMatterGaugePanel @@ -25,7 +26,7 @@ class MatterDecomposerScreen(p_97741_: MatterDecomposerMenu, p_97742_: Inventory BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - SlotPanel(this, frame, menu.input, 122f, PROGRESS_SLOT_TOP) + UserFilteredSlotPanel(this, frame, menu.input, 122f, PROGRESS_SLOT_TOP) ProgressGaugePanel(this, frame, menu.progressWidget, 96f, PROGRESS_ARROW_TOP).also { it.flop = true } SlotPanel(this, frame, menu.outputMain, 74f, PROGRESS_SLOT_TOP) SlotPanel(this, frame, menu.outputStacking, 56f, PROGRESS_SLOT_TOP) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterEntanglerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterEntanglerScreen.kt index e29d26246..1d7777813 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterEntanglerScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterEntanglerScreen.kt @@ -11,6 +11,7 @@ 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.slot.UserFilteredSlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel import ru.dbotthepony.mc.otm.compat.jei.MatterEntanglerRecipeCategory @@ -30,7 +31,7 @@ class MatterEntanglerScreen(menu: MatterEntanglerMenu, inventory: Inventory, tit it.dockResize = DockResizeMode.NONE for (slot in menu.inputs) - SlotPanel(this, it, slot) + UserFilteredSlotPanel(this, it, slot) } ProgressGaugePanel(this, frame, menu.progress).also { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReconstructorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReconstructorScreen.kt index 7d9d99dff..2522480d6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReconstructorScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterReconstructorScreen.kt @@ -10,9 +10,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.PlayerEquipmentPanel import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls import ru.dbotthepony.mc.otm.client.screen.panels.makeCuriosPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel -import ru.dbotthepony.mc.otm.client.screen.widget.MatterGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel 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 @@ -27,7 +25,7 @@ class MatterReconstructorScreen(menu: MatterReconstructorMenu, inventory: Invent BatterySlotPanel(this, frame, menu.batterySlot, LEFT_MARGIN, SLOT_TOP_UNDER_GAUGE) - SlotPanel(this, frame, menu.slot, 66f, PROGRESS_SLOT_TOP) + UserFilteredSlotPanel(this, frame, menu.slot, 66f, PROGRESS_SLOT_TOP) ProgressGaugePanel(this, frame, menu.progress, 37f, PROGRESS_ARROW_TOP) makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, itemConfig = menu.itemConfig, energyConfig = menu.energyConfig, upgrades = menu.upgrades) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt index 330fa3ef7..40d553e7b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EffectListPanel.kt @@ -62,7 +62,7 @@ open class EffectListPanel @JvmOverloads constructor( init { scroll.visible = false - //scissor = true + scissor = true } open inner class EffectSquare( diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/PlayerEquipmentPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/PlayerEquipmentPanel.kt index b28d2350d..bbb511306 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/PlayerEquipmentPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/PlayerEquipmentPanel.kt @@ -60,7 +60,7 @@ open class PlayerEquipmentPanel>( parent: EditablePanel<*>?, x: Float = 0f, y: Float = 0f, - val armorSlots: List> + val armorSlots: List> ) : EditablePanel(screen, parent, x, y, height = HEIGHT, width = WIDTH) { val armorSlotsStrip = EditablePanel(screen, this, width = AbstractSlotPanel.SIZE) val entityPanel = EntityRendererPanel(screen, this, minecraft.player!!) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/QuickStackControlsPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/QuickStackControlsPanel.kt new file mode 100644 index 000000000..c0616b598 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/QuickStackControlsPanel.kt @@ -0,0 +1,120 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import com.mojang.blaze3d.platform.InputConstants +import net.minecraft.ChatFormatting +import net.minecraft.client.gui.screens.Screen +import net.neoforged.neoforge.network.PacketDistributor +import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable +import ru.dbotthepony.mc.otm.client.render.Widgets18 +import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.menu.QuickMoveInput +import ru.dbotthepony.mc.otm.network.QuickStackPacket +import java.util.function.IntConsumer + +class QuickStackControlsPanel( + screen: S, + parent: EditablePanel<*>? = null, + x: Float = 0f, + y: Float = 0f +) : EditablePanel(screen, parent, x, y, 18f, 18f) { + private val grid = GridPanel(screen, this, columns = 1, rows = 1) + private val buttons = ArrayList>() + + init { + grid.dock = Dock.FILL + grid.layout = GridPanel.Layout.TOP_RIGHT + grid.columnMajorOrder = true + + // so row with main button contains only that button (visibly) + val fill = EditablePanel(screen, grid, width = 18f, height = 18f) + fill.childrenOrder = 999 + fill.visible = false + fill.dockMargin = DockProperty.bottomRight(1f) + buttons.add(fill) + } + + private inner class MainButton : ButtonPanel(screen, grid, width = 18f, height = 18f) { + init { + dockMargin = DockProperty.bottom(1f) + childrenOrder = -100000 + tooltips.add(TranslatableComponent("otm.gui.quickmove.exchange")) + tooltips.add(TranslatableComponent("otm.gui.quickmove.exchange.desc").withStyle(ChatFormatting.GRAY)) + tooltips.add(TextComponent("")) + tooltips.add(TranslatableComponent("otm.gui.quickmove_hint").withStyle(ChatFormatting.GRAY)) + } + + override var isDisabled: Boolean + get() = minecraft.player!!.isSpectator + set(value) {} + + override val icon: IGUIRenderable + get() = Widgets18.SMART_STORAGE_EXCHANGE + + override fun test(value: Int): Boolean { + return value == InputConstants.MOUSE_BUTTON_LEFT || value == InputConstants.MOUSE_BUTTON_RIGHT + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + PacketDistributor.sendToServer(QuickStackPacket(QuickMoveInput.Mode.RESTOCK_WITH_MOVE, true)) + PacketDistributor.sendToServer(QuickStackPacket(QuickMoveInput.Mode.RESTOCK, false)) + } else { + buttons.forEach { it.visible = !it.visible } + + if (buttons[0].visible) { + grid.columns = 2 + grid.rows = 4 + } else { + grid.columns = 1 + grid.rows = 1 + } + + this@QuickStackControlsPanel.sizeToContents() + } + } + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + return false + } + + override fun sizeToContents() { + val oldWidth = width + super.sizeToContents() + x -= width - oldWidth + } + + init { + for ((i, mode) in QuickMoveInput.Mode.entries.withIndex()) { + val button = ButtonPanel.square18( + screen, grid, + mode.iconFromStorage, + onPress = IntConsumer { PacketDistributor.sendToServer(QuickStackPacket(mode, false)) }) + + button.childrenOrder = 1000 + i + button.dockMargin = DockProperty.bottomRight(1f) + button.visible = false + button.tooltips.add(mode.textFromStorage) + buttons.add(button) + } + + for ((i, mode) in QuickMoveInput.Mode.entries.withIndex()) { + val button = ButtonPanel.square18( + screen, grid, + mode.iconToStorage, + onPress = IntConsumer { PacketDistributor.sendToServer(QuickStackPacket(mode, true)) }) + + button.childrenOrder = i + button.dockMargin = DockProperty.bottom(1f) + button.visible = false + button.tooltips.add(mode.textToStorage) + buttons.add(button) + } + + MainButton() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt index f513a81c4..d94a736cc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/Buttons.kt @@ -2,12 +2,11 @@ package ru.dbotthepony.mc.otm.client.screen.panels.button import com.mojang.blaze3d.platform.InputConstants import net.minecraft.ChatFormatting -import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items -import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.util.Delegate +import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.value import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting @@ -21,7 +20,6 @@ import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.IGUIRenderable import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite import ru.dbotthepony.mc.otm.client.render.ItemStackIcon -import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.Dock @@ -38,8 +36,9 @@ import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.util.ItemStackSorter import ru.dbotthepony.mc.otm.core.util.getLevelFromXp -import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.QuickMoveInput +import ru.dbotthepony.mc.otm.menu.SortInput import ru.dbotthepony.mc.otm.menu.UpgradeSlots import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput @@ -388,13 +387,17 @@ class DeviceControls>( } fun

> addButton(button: P): P { - buttons.add(button) - button.parent = this - alignButtons() + if (button !in buttons) { + buttons.add(button) + button.parent = this + alignButtons() + } + return button } fun

> addButton(button: P, after: EditablePanel<*>): P { + buttons.remove(button) val index = buttons.indexOf(after) if (index == -1) throw NoSuchElementException("Unknown panel to add button after: $after") buttons.add(index + 1, button) @@ -404,9 +407,12 @@ class DeviceControls>( } fun

> prependButton(button: P): P { - buttons.add(0, button) - button.parent = this - alignButtons() + if (button !in buttons) { + buttons.add(0, button) + button.parent = this + alignButtons() + } + return button } @@ -452,9 +458,74 @@ class DeviceControls>( return result } - fun sortingButtons(input: MatteryMenu.SortInput, unfoldableSettings: Boolean = true) { - object : ButtonPanel(screen, this@DeviceControls, width = 18f, height = 18f) { - var buttons: List>? = null + abstract inner class FoldableButtonPanel() : ButtonPanel(screen, this@DeviceControls, width = 18f, height = 18f) { + init { + addButton(this) + } + + private var buttons: List>? = null + + fun makeButtons() { + if (buttons == null) { + buttons = doMakeButtons().also { + it.forEach { addButton(it, this) } + } + } + } + + fun removeButtons() { + if (buttons != null) { + buttons!!.forEach { it.remove() } + buttons = null + } + } + + override fun test(value: Int): Boolean { + return value == InputConstants.MOUSE_BUTTON_LEFT || hasExtraButtons && value == InputConstants.MOUSE_BUTTON_RIGHT + } + + fun switchButtons() { + if (buttons == null) + makeButtons() + else + removeButtons() + } + + protected abstract val hasExtraButtons: Boolean + protected abstract fun performAction() + protected abstract fun doMakeButtons(): List> + + final override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + performAction() + } else { + switchButtons() + } + } + } + + fun sortingButtons(input: SortInput, unfoldableSettings: Boolean = true) { + object : FoldableButtonPanel() { + override fun performAction() { + input.clientInput() + } + + override var isDisabled: Boolean + get() { return !input.input.test(minecraft.player ?: return false) } + set(value) {} + + override val hasExtraButtons: Boolean + get() = unfoldableSettings + + override fun doMakeButtons(): List> { + return sortingButtons(Delegate.Of(input.settings::isAscending), Delegate.Of(input.settings::sorting), ItemStackSorter.DEFAULT) { + for (v in ItemStackSorter.entries) { + add(v, v.icon, v.title) + } + + finish() + } + } init { tooltips.add(TranslatableComponent("otm.gui.sorting.sort_now")) @@ -462,50 +533,75 @@ class DeviceControls>( if (unfoldableSettings) { tooltips.add(TextComponent("")) tooltips.add(TranslatableComponent("otm.gui.sorting.sort_settings").withStyle(ChatFormatting.GRAY)) - } - addButton(this) - - if (!unfoldableSettings) { + } else { makeButtons() } } override val icon: IGUIRenderable get() = Widgets18.SORT_NOW + } + } - override fun test(value: Int): Boolean { - return value == InputConstants.MOUSE_BUTTON_LEFT || unfoldableSettings && value == InputConstants.MOUSE_BUTTON_RIGHT + fun quickMoveButtons( + buttons: Map, + fromStorage: Map? = null + ) { + object : FoldableButtonPanel() { + override fun performAction() { + if (fromStorage == null) { + buttons[QuickMoveInput.Mode.MOVE]!!.clientInput() + } else { + buttons[QuickMoveInput.Mode.RESTOCK_WITH_MOVE]!!.clientInput() + fromStorage[QuickMoveInput.Mode.RESTOCK]!!.clientInput() + } } override var isDisabled: Boolean - get() { return !input.input.test(minecraft.player ?: return false) } + get() { return !buttons.values.first().input.test(minecraft.player ?: return false) } set(value) {} - private fun makeButtons() { - buttons = sortingButtons(Delegate.Of(input.settings::isAscending), Delegate.Of(input.settings::sorting), ItemStackSorter.DEFAULT) { - for (v in ItemStackSorter.entries) { - add(v, v.icon, v.title) + override val hasExtraButtons: Boolean + get() = true + + override fun doMakeButtons(): List> { + var stream = buttons.entries + .stream() + + if (fromStorage == null) + stream = stream.filter { (m) -> m != QuickMoveInput.Mode.MOVE } + + return stream + .map { (m, b) -> + square18( + screen, + this, + icon = if (fromStorage == null) m.iconFromStorage else m.iconToStorage, + onPress = { b.clientInput() } + ).also { + if (fromStorage == null) + it.tooltips.add(m.textFromStorage) + else + it.tooltips.add(m.textToStorage) + } } - - finish() - } - - buttons!!.forEach { removeButton(it) } - buttons!!.forEach { addButton(it as EditablePanel, this) } + .toList() } - override fun onClick(mouseButton: Int) { - if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { - input.clientInput() - } else { - if (buttons == null) { - makeButtons() - } else { - buttons!!.forEach { it.remove() } - buttons = null - } + init { + if (fromStorage == null) + tooltips.add(QuickMoveInput.Mode.MOVE.textFromStorage) + else { + tooltips.add(TranslatableComponent("otm.gui.quickmove.exchange")) + tooltips.add(TranslatableComponent("otm.gui.quickmove.exchange.desc").withStyle(ChatFormatting.GRAY)) } + + tooltips.add(TextComponent("")) + tooltips.add(TranslatableComponent("otm.gui.quickmove_hint").withStyle(ChatFormatting.GRAY)) } + + override val icon: IGUIRenderable + get() = if (fromStorage == null) QuickMoveInput.Mode.MOVE.iconFromStorage else Widgets18.SMART_STORAGE_EXCHANGE } } @@ -571,7 +667,7 @@ class DeviceControls>( val grid = GridPanel(screen, frame, columns = columns, rows = rows) for (slot in upgrades.slots) { - object : SlotPanel(screen, grid, slot) { + object : SlotPanel(screen, grid, slot) { override val cursorType: CursorType get() = if (upgrades.areLocked.get()) CursorType.NOT_ALLOWED else super.cursorType } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt index b3ebe2cab..698ce1a91 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/FilterSlotPanel.kt @@ -4,22 +4,36 @@ import net.minecraft.world.item.ItemStack import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel +import ru.dbotthepony.mc.otm.container.ItemFilter -open class FilterSlotPanel> @JvmOverloads constructor( +open class FilterSlotPanel>( screen: S, parent: EditablePanel<*>?, - val slot: Delegate, + val slot: Delegate, x: Float = 0f, y: Float = 0f, width: Float = SIZE, height: Float = SIZE ) : AbstractSlotPanel(screen, parent, x, y, width, height) { + private var lastFilteredItemDisplayUpdate = System.nanoTime() + private var filteredItemDisplayIndex = 0 + override val itemStack: ItemStack get() { - return slot.get() + val items = slot.get().displayItems + + if (items.isEmpty()) + return ItemStack.EMPTY + + if (System.nanoTime() - lastFilteredItemDisplayUpdate >= 1_000_000_000L || filteredItemDisplayIndex !in items.indices) { + lastFilteredItemDisplayUpdate = System.nanoTime() + filteredItemDisplayIndex = random.nextInt(items.size) + } + + return items.asList()[filteredItemDisplayIndex].asItemStack() } override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - slot.accept(screen.menu.carried) + slot.accept(ItemFilter.item(screen.menu.carried)) return true } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/InventorySlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/InventorySlotPanel.kt index caa01f6aa..09588c48d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/InventorySlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/InventorySlotPanel.kt @@ -18,10 +18,6 @@ open class InventorySlotPanel, out T : MatteryMenu.Inve x: Float = 0f, y: Float = 0f, ) : UserFilteredSlotPanel(screen, parent, slot, x, y, SIZE, SIZE) { - override var slotFilter: Item? - get() = slot.filter?.get() - set(value) { slot.filter?.accept(value) } - override fun renderBackgroundBeforeFilter(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { if (slot.chargeFlag?.get() == true) { Widgets18.CHARGE_SLOT_BACKGROUND.render(graphics, 0f, 0f, width, height) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt index e7139cff1..3ebc0be4a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/SlotPanel.kt @@ -5,9 +5,13 @@ package ru.dbotthepony.mc.otm.client.screen.panels.slot import com.mojang.blaze3d.systems.RenderSystem import net.minecraft.ChatFormatting import net.minecraft.client.renderer.GameRenderer +import net.minecraft.network.chat.Component import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions +import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.Widgets18 @@ -15,6 +19,14 @@ import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.compat.itemborders.isItemBordersLoaded import ru.dbotthepony.mc.otm.compat.itemborders.renderSlotBorder +import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemStackKey +import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import javax.annotation.Nonnull import kotlin.math.roundToInt @@ -52,6 +64,49 @@ open class SlotPanel, out T : Slot>( } } + protected open fun renderBackgroundBeforeFilter(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {} + + private var lastFilteredItemDisplayUpdate = System.nanoTime() + private var filteredItemDisplayIndex = 0 + + private fun selectRandomItemFromFilter(filter: ItemFilter): ItemStack { + val items = filter.displayItems + + if (items.isEmpty()) { + return ItemStack.EMPTY + } else { + if (System.nanoTime() - lastFilteredItemDisplayUpdate >= 1_000_000_000L || filteredItemDisplayIndex !in items.indices) { + lastFilteredItemDisplayUpdate = System.nanoTime() + filteredItemDisplayIndex = random.nextInt(items.size) + } + + return items.asList()[filteredItemDisplayIndex].asItemStack() + } + } + + override fun renderSlotBackground(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + super.renderSlotBackground(graphics, mouseX, mouseY, partialTick) + + val containerSlot = slot.container.containerSlotOrNull(slot.slotIndex) + + if (containerSlot is IFilteredContainerSlot) { + renderBackgroundBeforeFilter(graphics, mouseX, mouseY, partialTick) + + if (containerSlot.filter.denyAll) { + graphics.renderRect(0f, 0f, width, height, color = SLOT_BLOCK_COLOR) + } else if (!containerSlot.filter.allowAll) { + val itemStack = selectRandomItemFromFilter(containerSlot.filter) + + if (itemStack.isNotEmpty) { + screen.renderItemStack(graphics, itemStack, null) + clearDepth(graphics) + + graphics.renderRect(0f, 0f, width, height, color = SLOT_FILTER_COLOR) + } + } + } + } + override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { slot.x = absoluteX.roundToInt() - screen.guiLeft slot.y = absoluteY.roundToInt() - screen.guiTop @@ -120,10 +175,58 @@ open class SlotPanel, out T : Slot>( } } - override fun innerRenderTooltips(@Nonnull graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + val slot = slot.container.containerSlotOrNull(slot.containerSlot) as? IFilteredContainerSlot + + if (isHovered && slot?.filter != null && slot.filter.hasRules && itemStack.isEmpty) { + val itemstack = selectRandomItemFromFilter(slot.filter) + + val text: List + + if (itemstack.isEmpty) { + text = listOf( + TranslatableComponent("otm.gui.slot_filter.filtered").withStyle(ChatFormatting.GRAY), + TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY) + ) + } else { + text = getItemStackTooltip(itemstack).toMutableList().also { + it.add(0, TranslatableComponent("otm.gui.slot_filter.filtered").withStyle(ChatFormatting.GRAY)) + it.add(1, TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY)) + it.add(2, TextComponent("")) + } + } + + graphics.renderComponentTooltip( + IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: font, + text, + mouseX.toInt(), + mouseY.toInt(), + itemstack + ) + + return true + } else if (isHovered && slot?.filter?.denyAll == true && itemStack.isEmpty) { + graphics.renderComponentTooltip( + font, + ArrayList().also { + it.add(TranslatableComponent("otm.gui.slot_filter.forbidden").withStyle(ChatFormatting.GRAY)) + it.add(TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY)) + }, + mouseX.toInt(), + mouseY.toInt() + ) + + return true + } + // no op, screen does it for us (completely) return false } + + companion object { + val SLOT_FILTER_COLOR = RGBAColor(85, 113, 216, 150) + val SLOT_BLOCK_COLOR = RGBAColor(219, 113, 113, 150) + } } fun , T : Slot> BatterySlotPanel( @@ -134,7 +237,7 @@ fun , T : Slot> BatterySlotPanel( y: Float = 0f, width: Float = AbstractSlotPanel.SIZE, height: Float = AbstractSlotPanel.SIZE, -) = SlotPanel(screen, parent, slot, x, y, width, height).also { it.slotBackgroundEmpty = Widgets18.BATTERY_SLOT_BACKGROUND } +) = (if (slot is UserFilteredMenuSlot) UserFilteredSlotPanel(screen, parent, slot, x, y, width, height) else SlotPanel(screen, parent, slot, x, y, width, height)).also { it.slotBackgroundEmpty = Widgets18.BATTERY_SLOT_BACKGROUND } fun , T : Slot> EquipmentBatterySlotPanel( screen: S, @@ -144,7 +247,7 @@ fun , T : Slot> EquipmentBatterySlotPanel( y: Float = 0f, width: Float = AbstractSlotPanel.SIZE, height: Float = AbstractSlotPanel.SIZE, -) = SlotPanel(screen, parent, slot, x, y, width, height).also { it.slotBackgroundEmpty = Widgets18.EQUIPMENT_BATTERY_SLOT_BACKGROUND } +) = (if (slot is UserFilteredMenuSlot) UserFilteredSlotPanel(screen, parent, slot, x, y, width, height) else SlotPanel(screen, parent, slot, x, y, width, height)).also { it.slotBackgroundEmpty = Widgets18.EQUIPMENT_BATTERY_SLOT_BACKGROUND } fun , T : Slot> PatternSlotPanel( screen: S, @@ -154,7 +257,7 @@ fun , T : Slot> PatternSlotPanel( y: Float = 0f, width: Float = AbstractSlotPanel.SIZE, height: Float = AbstractSlotPanel.SIZE, -) = SlotPanel(screen, parent, slot, x, y, width, height).also { it.slotBackgroundEmpty = Widgets18.PATTERN_SLOT_BACKGROUND } +) = (if (slot is UserFilteredMenuSlot) UserFilteredSlotPanel(screen, parent, slot, x, y, width, height) else SlotPanel(screen, parent, slot, x, y, width, height)).also { it.slotBackgroundEmpty = Widgets18.PATTERN_SLOT_BACKGROUND } fun , T : Slot> MatterCapacitorSlotPanel( screen: S, @@ -164,4 +267,4 @@ fun , T : Slot> MatterCapacitorSlotPanel( y: Float = 0f, width: Float = AbstractSlotPanel.SIZE, height: Float = AbstractSlotPanel.SIZE, -) = SlotPanel(screen, parent, slot, x, y, width, height).also { it.slotBackgroundEmpty = Widgets18.MATTER_CAPACITOR_SLOT_BACKGROUND } +) = (if (slot is UserFilteredMenuSlot) UserFilteredSlotPanel(screen, parent, slot, x, y, width, height) else SlotPanel(screen, parent, slot, x, y, width, height)).also { it.slotBackgroundEmpty = Widgets18.MATTER_CAPACITOR_SLOT_BACKGROUND } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt index 86c93d76a..93edb8608 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/UserFilteredSlotPanel.kt @@ -1,28 +1,17 @@ package ru.dbotthepony.mc.otm.client.screen.panels.slot import com.mojang.blaze3d.platform.InputConstants -import net.minecraft.ChatFormatting -import net.minecraft.network.chat.Component -import net.minecraft.world.inventory.Slot -import net.minecraft.world.item.Item -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items -import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions -import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.isCtrlDown import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.playGuiClickSound import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -import ru.dbotthepony.mc.otm.core.TextComponent -import ru.dbotthepony.mc.otm.core.TranslatableComponent -import ru.dbotthepony.kommons.math.RGBAColor -import ru.dbotthepony.kommons.util.Delegate -import ru.dbotthepony.kommons.util.getValue -import ru.dbotthepony.kommons.util.setValue -import ru.dbotthepony.mc.otm.menu.UserFilteredSlot +import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.util.containerSlot +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot -abstract class UserFilteredSlotPanel, out T : Slot>( +open class UserFilteredSlotPanel, out T : UserFilteredMenuSlot>( screen: S, parent: EditablePanel<*>?, slot: T, @@ -31,77 +20,22 @@ abstract class UserFilteredSlotPanel, out T : Slot>( width: Float = SIZE, height: Float = SIZE, ) : SlotPanel(screen, parent, slot, x, y, width, height) { - abstract var slotFilter: Item? - - protected open fun renderBackgroundBeforeFilter(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {} - - override fun renderSlotBackground(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { - super.renderSlotBackground(graphics, mouseX, mouseY, partialTick) - - renderBackgroundBeforeFilter(graphics, mouseX, mouseY, partialTick) - - if (slotFilter != null) { - if (slotFilter !== Items.AIR) { - val itemStack = ItemStack(slotFilter!!, 1) - - screen.renderItemStack(graphics, itemStack, null) - clearDepth(graphics) - - graphics.renderRect(0f, 0f, width, height, color = SLOT_FILTER_COLOR) - } else { - graphics.renderRect(0f, 0f, width, height, color = SLOT_BLOCK_COLOR) - } - } - } - - override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { - if (isHovered && slotFilter != null && slotFilter !== Items.AIR && itemStack.isEmpty) { - val itemstack = ItemStack(slotFilter!!, 1) - - graphics.renderComponentTooltip( - IClientItemExtensions.of(itemstack).getFont(itemstack, IClientItemExtensions.FontContext.TOOLTIP) ?: font, - getItemStackTooltip(itemstack).toMutableList().also { - it.add(0, TranslatableComponent("otm.gui.slot_filter.filtered").withStyle(ChatFormatting.GRAY)) - it.add(1, TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY)) - it.add(2, TextComponent("")) - }, - mouseX.toInt(), - mouseY.toInt(), - itemstack - ) - - return true - } else if (isHovered && slotFilter === Items.AIR && itemStack.isEmpty) { - graphics.renderComponentTooltip( - font, - ArrayList().also { - it.add(TranslatableComponent("otm.gui.slot_filter.forbidden").withStyle(ChatFormatting.GRAY)) - it.add(TranslatableComponent("otm.gui.slot_filter.hint").withStyle(ChatFormatting.GRAY)) - }, - mouseX.toInt(), - mouseY.toInt() - ) - - return true - } - - return super.innerRenderTooltips(graphics, mouseX, mouseY, partialTick) - } - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + val filterInput = slot.filterInput ?: return super.mouseClickedInner(x, y, button) + val containerSlot = slot.containerSlot() as IFilteredContainerSlot + if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isCtrlDown) { - if (slotFilter === null) { + if (containerSlot.filter.allowAll) { if (screen.menu.carried.isEmpty) { - slotFilter = slot.item.item + filterInput.accept(ItemFilter.item(slot.item.item)) } else { - slotFilter = screen.menu.carried.item + filterInput.accept(ItemFilter.item(screen.menu.carried.item)) } } else { - slotFilter = null + filterInput.accept(ItemFilter.EMPTY) } playGuiClickSound() - return true } else { return super.mouseClickedInner(x, y, button) @@ -115,40 +49,4 @@ abstract class UserFilteredSlotPanel, out T : Slot>( return super.mouseReleasedInner(x, y, button) } - - companion object { - val SLOT_FILTER_COLOR = RGBAColor(85, 113, 216, 150) - val SLOT_BLOCK_COLOR = RGBAColor(219, 113, 113, 150) - - fun , T : Slot> of( - screen: S, - parent: EditablePanel<*>?, - slot: T, - x: Float = 0f, - y: Float = 0f, - width: Float = SIZE, - height: Float = SIZE, - filter: Delegate - ): UserFilteredSlotPanel { - return object : UserFilteredSlotPanel(screen, parent, slot, x, y, width, height) { - override var slotFilter: Item? by filter - } - } - - fun , T : UserFilteredSlot> of( - screen: S, - parent: EditablePanel<*>?, - slot: T, - x: Float = 0f, - y: Float = 0f, - width: Float = SIZE, - height: Float = SIZE, - ): UserFilteredSlotPanel { - return object : UserFilteredSlotPanel(screen, parent, slot, x, y, width, height) { - override var slotFilter: Item? - get() = slot.filter?.get() - set(value) { slot.filter?.accept(value) } - } - } - } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt index 96c9022ee..5a6a5ffa9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/DriveViewerScreen.kt @@ -66,14 +66,12 @@ class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Comp settings.add(filterGrid) for (i in 0 until PortableCondensationDriveItem.MAX_FILTERS) { - FilterSlotPanel(this, filterGrid, menu.driveFilterSlots[i], 0f, 0f) + FilterSlotPanel(this, filterGrid, menu.driveFilter.slots[i], 0f, 0f) } settings.add(EditablePanel(this, frame, width = 90f).also { it.dock = Dock.LEFT - BooleanButtonPanel.Checkbox(this, it, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { it.dockTop = 20f; it.dock = Dock.TOP } - BooleanButtonPanel.Checkbox(this, it, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { it.dockTop = 4f; it.dock = Dock.TOP } - BooleanButtonPanel.Checkbox(this, it, menu.matchComponents, TranslatableComponent("otm.gui.filter.match_nbt")).also { it.dockTop = 4f; it.dock = Dock.TOP } + BooleanButtonPanel.Checkbox(this, it, menu.driveFilter.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { it.dockTop = 20f; it.dock = Dock.TOP } }) frame.CustomTab(view, activeIcon = ItemStackIcon(ItemStack(MItems.PORTABLE_CONDENSATION_DRIVE))) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt index a85e84860..4b1669648 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/StorageImporterExporterScreen.kt @@ -34,18 +34,6 @@ class StorageImporterExporterScreen(menu: StorageImporterExporterMenu, inventory it.childrenOrder = -1 } - BooleanButtonPanel.Checkbox(this, right, menu.filter.matchComponents, TranslatableComponent("otm.gui.filter.match_nbt")).also { - it.dock = Dock.BOTTOM - it.dockTop = 2f - it.childrenOrder = -2 - } - - BooleanButtonPanel.Checkbox(this, right, menu.filter.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { - it.dock = Dock.BOTTOM - it.dockTop = 2f - it.childrenOrder = -3 - } - makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig) return frame diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AbstractProcessingMachineScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AbstractProcessingMachineScreen.kt index 9409e8873..455788a82 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AbstractProcessingMachineScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AbstractProcessingMachineScreen.kt @@ -12,9 +12,11 @@ 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.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel import ru.dbotthepony.mc.otm.compat.jei.isJeiLoaded +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.tech.AbstractProcessingMachineMenu import kotlin.math.roundToInt @@ -50,9 +52,14 @@ open class AbstractProcessingMachineScreen(me row.dock = Dock.TOP row.dockBottom = 2f - SlotPanel(this, row, input).also { - it.dock = Dock.LEFT - } + if (input is UserFilteredMenuSlot) + UserFilteredSlotPanel(this, row, input).also { + it.dock = Dock.LEFT + } + else + SlotPanel(this, row, input).also { + it.dock = Dock.LEFT + } progressPanels.add(ProgressGaugePanel(this, row, progress).also { it.dock = Dock.LEFT diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt index c56e2283c..2e631ddc0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ChemicalGeneratorScreen.kt @@ -9,6 +9,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu @@ -35,7 +36,7 @@ class ChemicalGeneratorScreen(menu: ChemicalGeneratorMenu, inventory: Inventory, progress.setRecipeType { listOf(RecipeTypes.FUELING) } SlotPanel(this, frame, menu.residueSlot, 56f, PROGRESS_SLOT_TOP) - SlotPanel(this, frame, menu.fuelSlot, 104f, PROGRESS_SLOT_TOP) + UserFilteredSlotPanel(this, frame, menu.fuelSlot, 104f, PROGRESS_SLOT_TOP) makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, itemConfig = menu.itemConfig, energyConfig = menu.energyConfig) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyCounterScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyCounterScreen.kt index b552b93a1..8ddd297a5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyCounterScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EnergyCounterScreen.kt @@ -111,7 +111,19 @@ class EnergyCounterScreen(menu: EnergyCounterMenu, inventory: Inventory, title: } } - makeDeviceControls(this, frame, redstoneConfig = menu.redstone) + val controls = makeDeviceControls(this, frame, redstoneConfig = menu.redstone) + + controls.addButton( + BooleanButtonPanel.square18( + this, + controls, + menu.pullEnergyFromInput, + iconActive = Widgets18.LEFT_CONTROLS.push, + iconInactive = Widgets18.LEFT_CONTROLS.output, + tooltipActive = TranslatableComponent("block.overdrive_that_matters.energy_counter.do_pull"), + tooltipInactive = TranslatableComponent("block.overdrive_that_matters.energy_counter.dont_pull") + ) + ) return frame } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt index 3bab0a1a5..46a1d4ed4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/EssenceStorageScreen.kt @@ -21,6 +21,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls import ru.dbotthepony.mc.otm.client.screen.panels.input.TextInputPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.util.HorizontalStripPanel import ru.dbotthepony.mc.otm.core.ResourceLocation import ru.dbotthepony.mc.otm.core.TextComponent @@ -279,7 +280,7 @@ class EssenceStorageScreen(menu: EssenceStorageMenu, inventory: Inventory, title get() = SET_EXACT override fun onClick(mouseButton: Int) { - val player = minecraft!!.player!! ?: return + val player = minecraft!!.player!! if (player.experienceLevel == customDispense) { if (player.experienceProgress > 0f) { @@ -297,7 +298,7 @@ class EssenceStorageScreen(menu: EssenceStorageMenu, inventory: Inventory, title set(_) {} } - SlotPanel(this, inputs, menu.mendingSlot).also { + UserFilteredSlotPanel(this, inputs, menu.mendingSlot).also { it.dock = Dock.LEFT it.tooltips.add(TranslatableComponent("enchantment.minecraft.mending").withStyle(ChatFormatting.GRAY)) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ItemHatchScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ItemHatchScreen.kt index 7408515ea..913f5bdbf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ItemHatchScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/ItemHatchScreen.kt @@ -20,7 +20,7 @@ class ItemHatchScreen(menu: ItemHatchMenu, inventory: Inventory, title: Componen val grid = GridPanel(this, frame, 8f, 18f, 9f * 18f, 6f * 18f, 9, 6) for (slot in menu.storageSlots) - UserFilteredSlotPanel.of(this, grid, slot) + UserFilteredSlotPanel(this, grid, slot) if (menu.isInput) { val controls = DeviceControls(this, frame) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt index 8f2f3b46b..f4539471c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt @@ -6,7 +6,6 @@ import lain.mods.cos.impl.client.PlayerRenderHandler import lain.mods.cos.impl.client.gui.GuiCosArmorInventory import lain.mods.cos.impl.network.payload.PayloadSetSkinArmor import net.minecraft.client.gui.screens.Screen -import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.world.Container @@ -20,9 +19,6 @@ import net.neoforged.neoforge.network.PacketDistributor import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.mc.otm.client.render.MGUIGraphics import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.client.render.IGUIRenderable -import ru.dbotthepony.mc.otm.client.render.UVWindingOrder -import ru.dbotthepony.mc.otm.client.render.sprites.MatterySprite import ru.dbotthepony.mc.otm.client.render.sprites.sprite import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel @@ -32,7 +28,7 @@ import ru.dbotthepony.mc.otm.container.util.awareStream import ru.dbotthepony.mc.otm.container.util.iterator import ru.dbotthepony.mc.otm.core.collect.AwareItemStack import ru.dbotthepony.mc.otm.core.collect.emptyIterator -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import java.util.stream.Stream val isCosmeticArmorLoaded by lazy { @@ -47,7 +43,7 @@ val Player.cosmeticArmorSlots: Map? get() { return cosmeticArmorSlotsImpl } -private class CosmeticSlot(container: Container, private val slot: EquipmentSlot, private val player: Player) : MatterySlot(container, when (slot) { +private class CosmeticMenuSlot(container: Container, private val slot: EquipmentSlot, private val player: Player) : MatteryMenuSlot(container, when (slot) { EquipmentSlot.FEET -> 0 EquipmentSlot.LEGS -> 1 EquipmentSlot.CHEST -> 2 @@ -127,10 +123,10 @@ private val Player.cosmeticArmorSlotsImpl: Map? get() { } return mapOf( - EquipmentSlot.HEAD to CosmeticSlot(container, EquipmentSlot.HEAD, this), - EquipmentSlot.CHEST to CosmeticSlot(container, EquipmentSlot.CHEST, this), - EquipmentSlot.LEGS to CosmeticSlot(container, EquipmentSlot.LEGS, this), - EquipmentSlot.FEET to CosmeticSlot(container, EquipmentSlot.FEET, this), + EquipmentSlot.HEAD to CosmeticMenuSlot(container, EquipmentSlot.HEAD, this), + EquipmentSlot.CHEST to CosmeticMenuSlot(container, EquipmentSlot.CHEST, this), + EquipmentSlot.LEGS to CosmeticMenuSlot(container, EquipmentSlot.LEGS, this), + EquipmentSlot.FEET to CosmeticMenuSlot(container, EquipmentSlot.FEET, this), ) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt index 3c3b15906..5c2769876 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatterStorageProvider.kt @@ -13,8 +13,8 @@ import ru.dbotthepony.mc.otm.compat.jade.JadeUids import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.getCapability import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.getDecimal -import ru.dbotthepony.mc.otm.core.math.putDecimal +import ru.dbotthepony.mc.otm.core.nbt.getDecimal +import ru.dbotthepony.mc.otm.core.nbt.putDecimal import ru.dbotthepony.mc.otm.core.util.formatMatter import snownee.jade.api.BlockAccessor import snownee.jade.api.IBlockComponentProvider diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt index 56727d58a..d4291dbb6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryEnergyProvider.kt @@ -13,8 +13,8 @@ import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.mc.otm.capability.IProfiledStorage import ru.dbotthepony.mc.otm.core.getCapability -import ru.dbotthepony.mc.otm.core.math.getDecimal -import ru.dbotthepony.mc.otm.core.math.putDecimal +import ru.dbotthepony.mc.otm.core.nbt.getDecimal +import ru.dbotthepony.mc.otm.core.nbt.putDecimal import ru.dbotthepony.mc.otm.core.util.formatPower import snownee.jade.api.* import snownee.jade.api.config.IPluginConfig diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/AbstractVanillaChestMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/AbstractVanillaChestMenu.kt new file mode 100644 index 000000000..f7ccd1eb7 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/AbstractVanillaChestMenu.kt @@ -0,0 +1,34 @@ +package ru.dbotthepony.mc.otm.compat.vanilla + +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.MenuType +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.QuickMoveInput +import ru.dbotthepony.mc.otm.menu.SortInput + +abstract class AbstractVanillaChestMenu( + type: MenuType<*>, + containerId: Int, + inventory: Inventory, + val container: Container +) : MatteryMenu(type, containerId, inventory) { + abstract val rows: Int + abstract val columns: Int + + abstract val containerSlots: List + abstract val quickMoveToStorage: Map + abstract val quickMoveFromStorage: Map + val sort = SortInput(this, container, playerSortSettings) + + override fun stillValid(player: Player): Boolean { + return container.stillValid(player) + } + + override fun removed(player: Player) { + super.removed(player) + container.stopOpen(player) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt index 5393b66bf..0c5da9a7d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/ExtendedInventoryHandler.kt @@ -22,7 +22,7 @@ import ru.dbotthepony.mc.otm.player.MatteryPlayer import ru.dbotthepony.mc.otm.player.matteryPlayer import ru.dbotthepony.mc.otm.core.ResourceLocation import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import java.util.* private val menuConfigurations = WeakHashMap() @@ -47,9 +47,9 @@ private class MenuConfiguration( for (i in 0 .. 8) { if (matteryPlayer.exopackContainer.containerSize > i + offset) { - row.add(MatterySlot(matteryPlayer.exopackContainer, i + offset)) + row.add(MatteryMenuSlot(matteryPlayer.exopackContainer, i + offset)) } else { - row.add(FakeSlot()) + row.add(FakeMenuSlot()) } } @@ -150,7 +150,7 @@ private class MenuConfiguration( } } -private class FakeSlot : MatterySlot(SimpleContainer(1), 0, 0, 0) { +private class FakeMenuSlot : MatteryMenuSlot(SimpleContainer(1), 0, 0, 0) { override fun mayPickup(player: Player): Boolean { return false } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt index 42762af85..36c787e4e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestMenu.kt @@ -1,118 +1,78 @@ package ru.dbotthepony.mc.otm.compat.vanilla -import net.minecraft.core.registries.Registries 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.flag.FeatureFlags import net.minecraft.world.inventory.MenuType -import net.neoforged.bus.api.IEventBus -import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent -import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.QuickMoveInput import ru.dbotthepony.mc.otm.menu.makeSlots -import ru.dbotthepony.mc.otm.registry.MDeferredRegister class MatteryChestMenu( type: MenuType<*>, containerId: Int, - inventory: Inventory, val rows: Int, val columns: Int, - val container: Container = SimpleContainer(rows * columns) -) : MatteryMenu(type, containerId, inventory) { - val chestSlots = makeSlots(container, ::MatterySlot) - val sort = SortInput(container, playerSortSettings) + inventory: Inventory, override val rows: Int, override val columns: Int, + container: Container = EnhancedContainer.Simple(rows * columns) +) : AbstractVanillaChestMenu(type, containerId, inventory, container) { + override val containerSlots = makeSlots(container, ::MatteryMenuSlot) init { require(rows * columns == container.containerSize) { "Provided container $container has different dimensions than specified rows x columns: ${container.containerSize} vs $rows x $columns (${rows * columns})" } container.startOpen(player) - addStorageSlot(chestSlots) + addStorageSlot(containerSlots) addInventorySlots() } - override fun stillValid(player: Player): Boolean { - return container.stillValid(player) - } - - override fun removed(player: Player) { - super.removed(player) - container.stopOpen(player) - } + override val quickMoveToStorage = QuickMoveInput.create(this, playerCombinedInventorySlots, containerSlots) + override val quickMoveFromStorage = QuickMoveInput.create(this, containerSlots, playerInventorySlots, false) companion object { - private val registrar = MDeferredRegister(Registries.MENU, OverdriveThatMatters.MOD_ID) - - private val GENERIC_9x1 by registrar.register("generic_9x1") { MenuType(::c9x1, FeatureFlags.VANILLA_SET) } - private val GENERIC_9x2 by registrar.register("generic_9x2") { MenuType(::c9x2, FeatureFlags.VANILLA_SET) } - private val GENERIC_9x3 by registrar.register("generic_9x3") { MenuType(::c9x3, FeatureFlags.VANILLA_SET) } - private val GENERIC_9x4 by registrar.register("generic_9x4") { MenuType(::c9x4, FeatureFlags.VANILLA_SET) } - private val GENERIC_9x5 by registrar.register("generic_9x5") { MenuType(::c9x5, FeatureFlags.VANILLA_SET) } - private val GENERIC_9x6 by registrar.register("generic_9x6") { MenuType(::c9x6, FeatureFlags.VANILLA_SET) } - private val GENERIC_3x3 by registrar.register("generic_3x3") { MenuType(::c3x3, FeatureFlags.VANILLA_SET) } - private val HOPPER by registrar.register("hopper") { MenuType(::hopper, FeatureFlags.VANILLA_SET) } - @JvmStatic @JvmOverloads fun c9x1(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9)): MatteryChestMenu { - return MatteryChestMenu(GENERIC_9x1, containerId, inventory, 1, 9, container) + return MatteryChestMenu(VanillaMenuTypes.GENERIC_9x1, containerId, inventory, 1, 9, container) } @JvmStatic @JvmOverloads fun c9x2(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 2)): MatteryChestMenu { - return MatteryChestMenu(GENERIC_9x2, containerId, inventory, 2, 9, container) + return MatteryChestMenu(VanillaMenuTypes.GENERIC_9x2, containerId, inventory, 2, 9, container) } @JvmStatic @JvmOverloads fun c9x3(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 3)): MatteryChestMenu { - return MatteryChestMenu(GENERIC_9x3, containerId, inventory, 3, 9, container) + return MatteryChestMenu(VanillaMenuTypes.GENERIC_9x3, containerId, inventory, 3, 9, container) } @JvmStatic @JvmOverloads fun c9x4(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 4)): MatteryChestMenu { - return MatteryChestMenu(GENERIC_9x4, containerId, inventory, 4, 9, container) + return MatteryChestMenu(VanillaMenuTypes.GENERIC_9x4, containerId, inventory, 4, 9, container) } @JvmStatic @JvmOverloads fun c9x5(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 5)): MatteryChestMenu { - return MatteryChestMenu(GENERIC_9x5, containerId, inventory, 5, 9, container) + return MatteryChestMenu(VanillaMenuTypes.GENERIC_9x5, containerId, inventory, 5, 9, container) } @JvmStatic @JvmOverloads fun c9x6(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(9 * 6)): MatteryChestMenu { - return MatteryChestMenu(GENERIC_9x6, containerId, inventory, 6, 9, container) + return MatteryChestMenu(VanillaMenuTypes.GENERIC_9x6, containerId, inventory, 6, 9, container) } @JvmStatic @JvmOverloads fun c3x3(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(3 * 3)): MatteryChestMenu { - return MatteryChestMenu(GENERIC_3x3, containerId, inventory, 3, 3, container) + return MatteryChestMenu(VanillaMenuTypes.GENERIC_3x3, containerId, inventory, 3, 3, container) } @JvmStatic @JvmOverloads fun hopper(containerId: Int, inventory: Inventory, container: Container = SimpleContainer(5)): MatteryChestMenu { - return MatteryChestMenu(HOPPER, containerId, inventory, 1, 5, container) - } - - internal fun register(bus: IEventBus) { - registrar.register(bus) - bus.addListener(this::registerScreens) - } - - private fun registerScreens(event: RegisterMenuScreensEvent) { - event.register(GENERIC_9x1, ::MatteryChestScreen) - event.register(GENERIC_9x2, ::MatteryChestScreen) - event.register(GENERIC_9x3, ::MatteryChestScreen) - event.register(GENERIC_9x4, ::MatteryChestScreen) - event.register(GENERIC_9x5, ::MatteryChestScreen) - event.register(GENERIC_9x6, ::MatteryChestScreen) - event.register(GENERIC_3x3, ::MatteryChestScreen) - event.register(HOPPER, ::MatteryChestScreen) + return MatteryChestMenu(VanillaMenuTypes.HOPPER, containerId, inventory, 1, 5, container) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryShulkerBoxMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryShulkerBoxMenu.kt new file mode 100644 index 000000000..df242a70a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryShulkerBoxMenu.kt @@ -0,0 +1,36 @@ +package ru.dbotthepony.mc.otm.compat.vanilla + +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.QuickMoveInput +import ru.dbotthepony.mc.otm.menu.makeSlots + +class MatteryShulkerBoxMenu( + containerId: Int, + inventory: Inventory, + container: Container = EnhancedContainer.Simple(27) +) : AbstractVanillaChestMenu(VanillaMenuTypes.SHULKER_BOX, containerId, inventory, container) { + override val containerSlots = makeSlots(container, ::Slot) + override val rows: Int + get() = 3 + override val columns: Int + get() = 9 + + init { + container.startOpen(player) + addStorageSlot(containerSlots) + addInventorySlots() + } + + override val quickMoveToStorage = QuickMoveInput.create(this, playerCombinedInventorySlots, containerSlots) + override val quickMoveFromStorage = QuickMoveInput.create(this, containerSlots, playerInventorySlots, false) + + class Slot(container: Container, slot: Int) : MatteryMenuSlot(container, slot) { + override fun mayPlace(stack: ItemStack): Boolean { + return super.mayPlace(stack) && stack.item.canFitInsideContainerItems() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/VanillaChestScreen.kt similarity index 64% rename from src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestScreen.kt rename to src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/VanillaChestScreen.kt index 316a948c9..a782b0535 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/MatteryChestScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/VanillaChestScreen.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.compat.vanilla import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory +import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.mc.otm.client.render.RenderGravity import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.Dock @@ -10,8 +11,9 @@ 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.menu.QuickMoveInput -class MatteryChestScreen(menu: MatteryChestMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { +class VanillaChestScreen(menu: AbstractVanillaChestMenu, inventory: Inventory, title: Component) : MatteryScreen(menu, inventory, title) { override fun makeMainFrame(): FramePanel> { val frame = FramePanel.padded(this, AbstractSlotPanel.SIZE * menu.columns.coerceAtLeast(9), AbstractSlotPanel.SIZE * menu.rows + 4f, title) @@ -22,12 +24,20 @@ class MatteryChestScreen(menu: MatteryChestMenu, inventory: Inventory, title: Co grid.dock = Dock.FILL grid.gravity = RenderGravity.BOTTOM_CENTER - for (slot in menu.chestSlots) + for (slot in menu.containerSlots) SlotPanel(this, grid, slot) val controls = DeviceControls(this, frame) controls.sortingButtons(menu.sort) + val leftControls = DeviceControls(this, frame) + leftControls.dockOnLeft = true + leftControls.quickMoveButtons(menu.quickMoveFromStorage) + + val leftInventoryControls = DeviceControls(this, inventoryFrame!!) + leftInventoryControls.dockOnLeft = true + leftInventoryControls.quickMoveButtons(menu.quickMoveToStorage, menu.quickMoveFromStorage) + return frame } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/VanillaMenuTypes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/VanillaMenuTypes.kt new file mode 100644 index 000000000..fbca57f62 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/vanilla/VanillaMenuTypes.kt @@ -0,0 +1,71 @@ +package ru.dbotthepony.mc.otm.compat.vanilla + +import net.minecraft.core.BlockPos +import net.minecraft.core.registries.Registries +import net.minecraft.world.Container +import net.minecraft.world.flag.FeatureFlags +import net.minecraft.world.inventory.MenuType +import net.minecraft.world.level.Level +import net.minecraft.world.level.block.Blocks +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.entity.ChestBlockEntity +import net.minecraft.world.level.block.state.BlockState +import net.neoforged.bus.api.IEventBus +import net.neoforged.neoforge.capabilities.IBlockCapabilityProvider +import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent +import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.capability.IQuickStackContainer +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.makeSlots +import ru.dbotthepony.mc.otm.registry.MDeferredRegister + +object VanillaMenuTypes { + private val registrar = MDeferredRegister(Registries.MENU, OverdriveThatMatters.MOD_ID) + + val GENERIC_9x1 by registrar.register("generic_9x1") { MenuType(MatteryChestMenu::c9x1, FeatureFlags.VANILLA_SET) } + val GENERIC_9x2 by registrar.register("generic_9x2") { MenuType(MatteryChestMenu::c9x2, FeatureFlags.VANILLA_SET) } + val GENERIC_9x3 by registrar.register("generic_9x3") { MenuType(MatteryChestMenu::c9x3, FeatureFlags.VANILLA_SET) } + val GENERIC_9x4 by registrar.register("generic_9x4") { MenuType(MatteryChestMenu::c9x4, FeatureFlags.VANILLA_SET) } + val GENERIC_9x5 by registrar.register("generic_9x5") { MenuType(MatteryChestMenu::c9x5, FeatureFlags.VANILLA_SET) } + val GENERIC_9x6 by registrar.register("generic_9x6") { MenuType(MatteryChestMenu::c9x6, FeatureFlags.VANILLA_SET) } + val GENERIC_3x3 by registrar.register("generic_3x3") { MenuType(MatteryChestMenu::c3x3, FeatureFlags.VANILLA_SET) } + val HOPPER by registrar.register("hopper") { MenuType(MatteryChestMenu::hopper, FeatureFlags.VANILLA_SET) } + + val SHULKER_BOX by registrar.register("shulker_box") { MenuType(::MatteryShulkerBoxMenu, FeatureFlags.VANILLA_SET) } + + private fun provider(level: Level, pos: BlockPos, state: BlockState, blockEntity: BlockEntity?, context: Void?): IQuickStackContainer? { + val container = blockEntity as? Container ?: return null + return IQuickStackContainer.Simple(makeSlots(container, ::MatteryMenuSlot)) + } + + fun registerCapabilities(event: RegisterCapabilitiesEvent) { + event.registerBlock( + MatteryCapability.QUICK_STACK_CONTAINER, + ::provider, + Blocks.CHEST, + Blocks.TRAPPED_CHEST, + Blocks.SHULKER_BOX, + Blocks.BARREL, + ) + } + + internal fun register(bus: IEventBus) { + registrar.register(bus) + bus.addListener(this::registerScreens) + bus.addListener(this::registerCapabilities) + } + + private fun registerScreens(event: RegisterMenuScreensEvent) { + event.register(GENERIC_9x1, ::VanillaChestScreen) + event.register(GENERIC_9x2, ::VanillaChestScreen) + event.register(GENERIC_9x3, ::VanillaChestScreen) + event.register(GENERIC_9x4, ::VanillaChestScreen) + event.register(GENERIC_9x5, ::VanillaChestScreen) + event.register(GENERIC_9x6, ::VanillaChestScreen) + event.register(GENERIC_3x3, ::VanillaChestScreen) + event.register(HOPPER, ::VanillaChestScreen) + event.register(SHULKER_BOX, ::VanillaChestScreen) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt index b76938fb9..a74463904 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/AbstractConfig.kt @@ -7,7 +7,6 @@ import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.defineDecimal import ru.dbotthepony.mc.otm.core.util.WriteOnce abstract class AbstractConfig(private val configName: String, private val type: ModConfig.Type = ModConfig.Type.SERVER) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/CablesConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/CablesConfig.kt index fe5e269e3..ad00a469f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/CablesConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/CablesConfig.kt @@ -3,7 +3,6 @@ package ru.dbotthepony.mc.otm.config import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.defineDecimal object CablesConfig : AbstractConfig("cables") { enum class E(throughput: Decimal) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/DecimalConfigValue.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/DecimalConfigValue.kt new file mode 100644 index 000000000..457e734c9 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/DecimalConfigValue.kt @@ -0,0 +1,58 @@ +package ru.dbotthepony.mc.otm.config + +import net.neoforged.neoforge.common.ModConfigSpec +import ru.dbotthepony.mc.otm.core.math.Decimal + +class DecimalConfigValue( + parent: ModConfigSpec.ConfigValue, + val minimum: Decimal? = null, + val maximum: Decimal? = null, +) : ObservedConfigValue(parent) { + override fun fromString(value: String): Decimal? { + try { + val parsed = Decimal(value) + + if (minimum != null && minimum > parsed) { + return minimum + } else if (maximum != null && maximum < parsed) { + return maximum + } + + return parsed + } catch (err: java.lang.NumberFormatException) { + return null + } + } + + override fun toString(value: Decimal): Pair { + if (minimum != null && minimum > value) { + return minimum.toString() to minimum + } else if (maximum != null && maximum < value) { + return maximum.toString() to maximum + } + + return value.toString() to value + } +} + +private fun ModConfigSpec.Builder.commentRange(minimum: Decimal?, maximum: Decimal?) { + if (minimum != null && maximum != null) { + comment("Range: $minimum ~ $maximum") + } else if (minimum != null) { + comment("Range: >= $minimum") + } else if (maximum != null) { + comment("Range: <= $maximum") + } +} + +fun ModConfigSpec.Builder.defineDecimal(path: String, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue { + commentRange(minimum, maximum) + comment("Default: $defaultValue") + return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum) +} + +fun ModConfigSpec.Builder.defineDecimal(path: List, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue { + commentRange(minimum, maximum) + comment("Default: $defaultValue") + return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ExopackConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ExopackConfig.kt index afe73a16a..99f6fc655 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ExopackConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ExopackConfig.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.config import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.defineDecimal object ExopackConfig : AbstractConfig("exopack") { val ENERGY_CAPACITY by builder diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt index efe4c77ee..84e8bdb68 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ItemsConfig.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.config import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.defineDecimal import ru.dbotthepony.mc.otm.registry.MNames object ItemsConfig : AbstractConfig("items") { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt index 7b312c5ae..3eb517494 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.config import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.defineDecimal import ru.dbotthepony.mc.otm.registry.MNames object MachinesConfig : AbstractConfig("machines") { @@ -60,7 +59,7 @@ object MachinesConfig : AbstractConfig("machines") { private val MATTER_BOTTLER = workerValues( MNames.MATTER_BOTTLER, energyStorage = Decimal(40_000), - energyConsumption = Decimal(10), + energyConsumption = Decimal(40), energyThroughput = Decimal(200), matterCapacity = Decimal(400), workTimeMultiplier = null @@ -85,7 +84,7 @@ object MachinesConfig : AbstractConfig("machines") { object MatterBottler { val VALUES by ::MATTER_BOTTLER - val RATE by builder.comment("Matter transferred per tick").defineDecimal("RATE", Decimal("2.0"), Decimal.ONE_TENTH) + val RATE by builder.comment("Matter transferred per tick").defineDecimal("RATE", Decimal("10.0"), Decimal.ONE_TENTH) } object MatterRecycler { @@ -111,8 +110,8 @@ object MachinesConfig : AbstractConfig("machines") { private val MATTER_RECONSTRUCTOR = workerValues( MNames.MATTER_RECONSTRUCTOR, - energyStorage = Decimal(100_000), - energyThroughput = Decimal(1000), + energyStorage = Decimal(200_000), + energyThroughput = Decimal(4_000), energyConsumption = Decimal(400), matterCapacity = Decimal(200), workTimeMultiplier = null, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/PlayerConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/PlayerConfig.kt index c9f907244..d903cad0e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/PlayerConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/PlayerConfig.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.config import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.defineDecimal object PlayerConfig : AbstractConfig("player") { init { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerConfig.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerConfig.kt index 0687260bb..df5193062 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/ServerConfig.kt @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.config import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.defineDecimal object ServerConfig : AbstractConfig("misc") { val LABORATORY_LAMP_LIGHT_LENGTH: Int by builder.comment("In blocks").defineInRange("LABORATORY_LAMP_LIGHT_LENGTH", 6, 1, 128) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/BitmapTrackingContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/BitmapTrackingContainer.kt new file mode 100644 index 000000000..136990472 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/BitmapTrackingContainer.kt @@ -0,0 +1,61 @@ +package ru.dbotthepony.mc.otm.container + +import it.unimi.dsi.fastutil.ints.IntCollection +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.kommons.collect.iterateClearBits +import ru.dbotthepony.kommons.collect.iterateSetBits +import ru.dbotthepony.mc.otm.core.collect.IntRange2Set +import ru.dbotthepony.mc.otm.core.collect.map +import java.util.* + +abstract class BitmapTrackingContainer : IEnhancedContainer { + protected val bitmap = BitSet() + + final override fun isEmpty(): Boolean { + return bitmap.isEmpty + } + + final override fun nextEmptySlot(startIndex: Int): Int { + if (startIndex >= containerSize) + return -1 + else if (startIndex < 0) + return bitmap.nextClearBit(0) + else + return bitmap.nextClearBit(startIndex) + } + + final override fun nextNonEmptySlot(startIndex: Int): Int { + if (startIndex >= containerSize) + return -1 + else if (startIndex < 0) + return bitmap.nextSetBit(0) + else + return bitmap.nextSetBit(startIndex) + } + + final override fun iterator(): Iterator { + return bitmap.iterateSetBits(containerSize).map { this[it] } + } + + final override fun nonEmptySlotIndexIterator(): IntIterator { + return bitmap.iterateSetBits(containerSize) + } + + final override fun emptySlotIndexIterator(): IntIterator { + return bitmap.iterateClearBits(containerSize) + } + + final override fun emptySlotIndexIterator(allowedSlots: IntCollection): IntIterator { + if (allowedSlots is IntRange2Set && allowedSlots.isNotEmpty()) + return bitmap.iterateClearBits(allowedSlots.firstInt(), allowedSlots.lastInt() + 1) + + return super.emptySlotIndexIterator(allowedSlots) + } + + final override fun nonEmptySlotIndexIterator(allowedSlots: IntCollection): IntIterator { + if (allowedSlots is IntRange2Set && allowedSlots.isNotEmpty()) + return bitmap.iterateSetBits(allowedSlots.firstInt(), allowedSlots.lastInt() + 1) + + return super.nonEmptySlotIndexIterator(allowedSlots) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/CombinedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/CombinedContainer.kt index 542ebb252..ee5065ed5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/CombinedContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/CombinedContainer.kt @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableSet import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.ints.IntOpenHashSet import it.unimi.dsi.fastutil.ints.IntSet import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap @@ -22,39 +23,36 @@ import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.stream import java.util.stream.Stream -class CombinedContainer(containers: Stream>>) : IMatteryContainer { - constructor(vararg containers: Container) : this(containers.stream().map { it to (0 until it.containerSize) }) - constructor(containers: Collection) : this(containers.stream().map { it to (0 until it.containerSize) }) +class CombinedContainer(containers: Stream, Iterable>>) : ISlottedContainer { + constructor(vararg containers: IEnhancedContainer) : this(containers.stream().map { it to (0 until it.containerSize) }) + constructor(containers: Collection>) : this(containers.stream().map { it to (0 until it.containerSize) }) - private inner class Slot(override val slot: Int, val outer: IContainerSlot) : IContainerSlot by outer { - override val container: Container - get() = this@CombinedContainer - } - - private val slots: ImmutableList - private val slotsMap: ImmutableMap> + private val slots: ImmutableList + private val slotsMap: ImmutableMap> private val containers: ImmutableSet private val fullCoverage: ImmutableList - private val notFullCoverage: ImmutableMap> + private val notFullCoverage: ImmutableMap> init { - val list = ImmutableList.Builder() - var i = 0 + val list = ImmutableList.Builder() val validationMap = Reference2ObjectOpenHashMap() - val slotsMap = Reference2ObjectOpenHashMap>() + val slotsMap = Reference2ObjectOpenHashMap>() + var i = 0 for ((container, slots) in containers) { - val validator = validationMap.computeIfAbsent(container, Object2ObjectFunction { IntAVLTreeSet() }) + val validator = validationMap.computeIfAbsent(container, Object2ObjectFunction { IntOpenHashSet() }) val slotList = slotsMap.computeIfAbsent(container, Object2ObjectFunction { ArrayList() }) for (slot in slots) { if (validator.add(slot)) { val slotObj = container.containerSlot(slot) - list.add(Slot(i++, slotObj)) + list.add(slotObj) slotList.add(slotObj) } else { throw IllegalArgumentException("Duplicate mapping for $container at $i for slot $slot") } + + i++ } } @@ -78,6 +76,8 @@ class CombinedContainer(containers: Stream>>) : IM .collect(ImmutableMap.toImmutableMap({ it.key }, { it.value })) } + override val hasFilterableSlots: Boolean = super.hasFilterableSlots + override fun clearContent() { for (container in fullCoverage) { container.clearContent() @@ -85,20 +85,7 @@ class CombinedContainer(containers: Stream>>) : IM for (slots in notFullCoverage.values) { for (slot in slots) { - slot.item = ItemStack.EMPTY - } - } - } - - override fun clearSlotFilters() { - for (container in fullCoverage) { - if (container is IMatteryContainer) - container.clearSlotFilters() - } - - for (slots in notFullCoverage.values) { - for (slot in slots) { - slot.setFilter() + slot.remove() } } } @@ -108,37 +95,7 @@ class CombinedContainer(containers: Stream>>) : IM } override fun isEmpty(): Boolean { - if (fullCoverage.isNotEmpty()) - for (container in fullCoverage) - if (!container.isEmpty) - return false - - if (notFullCoverage.isNotEmpty()) - for (slots in notFullCoverage.values) - for (slot in slots) - if (!slot.isEmpty) - return false - - return true - } - - override fun getItem(slot: Int): ItemStack { - // do not violate contract of getItem not throwing exceptions when index is invalid - return slots.getOrNull(slot)?.item ?: ItemStack.EMPTY - } - - override fun removeItem(slot: Int, amount: Int): ItemStack { - val data = slots.getOrNull(slot) ?: return ItemStack.EMPTY - return data.outer.container.removeItem(data.outer.slot, amount) - } - - override fun removeItemNoUpdate(slot: Int): ItemStack { - val data = slots.getOrNull(slot) ?: return ItemStack.EMPTY - return data.outer.container.removeItemNoUpdate(data.outer.slot) - } - - override fun setItem(slot: Int, itemStack: ItemStack) { - slots.getOrNull(slot)?.item = itemStack + return fullCoverage.all { it.isEmpty } && notFullCoverage.values.all { it.all { it.isEmpty } } } override fun setChanged() { @@ -148,82 +105,87 @@ class CombinedContainer(containers: Stream>>) : IM } override fun stillValid(player: Player): Boolean { - for (container in containers) - if (!container.stillValid(player)) - return false - - return true + return containers.all { it.stillValid(player) } } - override fun iterator(nonEmpty: Boolean): Iterator { + override fun iterator(): Iterator { if (notFullCoverage.isEmpty()) - return fullCoverage.iterator().flatMap { it.iterator(nonEmpty) } + return fullCoverage.iterator().flatMap { it.iterator() } return concatIterators( - fullCoverage.iterator().flatMap { it.iterator(nonEmpty) }, - notFullCoverage.values.iterator().flatMap { it.iterator() }.map { it.item }.let { if (nonEmpty) it.filter { it.isNotEmpty } else it } + fullCoverage.iterator().flatMap { it.iterator() }, + notFullCoverage.values.iterator().flatMap { it.iterator() }.map { it.item }.filter { it.isNotEmpty } ) } - override fun slotIterator(nonEmpty: Boolean): Iterator { - if (nonEmpty) - return slots.iterator().filter { it.isNotEmpty } - + override fun slotIterator(): Iterator { return slots.iterator() } - override fun containerSlot(slot: Int): IContainerSlot { + override fun containerSlot(slot: Int): S { return slots[slot] } - override fun getSlotFilter(slot: Int): Item? { - return slots[slot].getFilter() - } - override fun setChanged(slot: Int) { slots[slot].setChanged() } - override fun setSlotFilter(slot: Int, filter: Item?): Boolean { - return slots[slot].setFilter(filter) - } + class Builder { + private val values = ArrayList, Iterable>>() - class Builder { - private val values = ArrayList>>() + fun add(container: Container): Builder { + return add(IEnhancedContainer.wrap(container)) + } - fun add(container: Container): Builder { + fun add(container: Container, slots: Iterator): Builder { + return add(IEnhancedContainer.wrap(container), slots) + } + + fun add(container: Container, slot: Int): Builder { + return add(IEnhancedContainer.wrap(container), slot) + } + + fun add(container: Container, from: Int, to: Int): Builder { + return add(IEnhancedContainer.wrap(container), from, to) + } + + fun add(container: Container, slots: Iterable): Builder { + return add(IEnhancedContainer.wrap(container), slots) + } + + fun add(container: IEnhancedContainer): Builder { values.add(container to container.slotRange) return this } - fun add(container: Container, slots: Iterator): Builder { + fun add(container: IEnhancedContainer, slots: Iterator): Builder { values.add(container to IntArrayList(slots)) return this } - fun add(container: Container, slot: Int): Builder { + fun add(container: IEnhancedContainer, slot: Int): Builder { values.add(container to intArrayOf(slot).asIterable()) return this } - fun add(container: Container, from: Int, to: Int): Builder { + fun add(container: IEnhancedContainer, from: Int, to: Int): Builder { values.add(container to (from .. to)) return this } - fun add(container: Container, slots: Iterable): Builder { + fun add(container: IEnhancedContainer, slots: Iterable): Builder { values.add(container to slots) return this } - fun build(): CombinedContainer { + fun build(): CombinedContainer { return CombinedContainer(values.stream()) } } companion object { - fun fromMenuSlots(slots: Iterator): CombinedContainer { - val builder = Builder() + fun fromMenuSlots(slots: Iterator): CombinedContainer { + val builder = Builder() for (slot in slots) { builder.add(slot.container, slot.slotIndex) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt deleted file mode 100644 index f3c21eb2d..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHandler.kt +++ /dev/null @@ -1,89 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import net.minecraft.world.item.ItemStack -import net.neoforged.neoforge.items.IItemHandler - -class ContainerHandler( - private val container: IMatteryContainer, - private val filter: HandlerFilter = HandlerFilter.Both, -) : IItemHandler { - override fun getSlots() = container.containerSize - override fun getStackInSlot(slot: Int) = container[slot] - - override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { - if (!container.testSlotFilter(slot, stack) || !filter.canInsert(slot, stack)) - return stack - - filter.preInsert(slot, stack, simulate) - - val localStack = container[slot] - var amount = filter.modifyInsertCount(slot, stack, localStack, simulate) - - if (amount <= 0) - return stack - - if (localStack.isEmpty) { - amount = stack.count.coerceAtMost(container.getMaxStackSize(slot, stack)).coerceAtMost(amount) - - if (!simulate) { - container.setItem(slot, stack.copyWithCount(amount)) - } - - if (stack.count <= amount) { - return ItemStack.EMPTY - } else { - return stack.copyWithCount(stack.count - amount) - } - } else if (localStack.isStackable && container.getMaxStackSize(slot, localStack) > localStack.count && ItemStack.isSameItemSameComponents(localStack, stack)) { - val newCount = container.getMaxStackSize(slot, localStack).coerceAtMost(localStack.count + stack.count.coerceAtMost(amount)) - val diff = newCount - localStack.count - - if (diff != 0) { - if (!simulate) { - localStack.grow(diff) - container.setChanged(slot) - } - - val copy = stack.copy() - copy.shrink(diff) - return copy - } - } - - return stack - } - - override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { - if (amount <= 0 || container.isSlotForbiddenForAutomation(slot)) - return ItemStack.EMPTY - - val localStack = container.getItem(slot) - if (localStack.isEmpty) return ItemStack.EMPTY - - @Suppress("name_shadowing") - val amount = filter.modifyExtractCount(slot, amount, simulate) - if (amount <= 0) return ItemStack.EMPTY - if (!filter.canExtract(slot, amount, localStack)) return ItemStack.EMPTY - - filter.preExtract(slot, amount, simulate) - - val minimal = amount.coerceAtMost(localStack.count) - val copy = localStack.copy() - copy.count = minimal - - if (!simulate) { - localStack.shrink(minimal) - container.setChanged(slot) - } - - return copy - } - - override fun getSlotLimit(slot: Int): Int { - return container.maxStackSize - } - - override fun isItemValid(slot: Int, stack: ItemStack): Boolean { - return container.testSlotFilter(slot, stack) && filter.canInsert(slot, stack) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt index 42f781e8f..6cee3f401 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerHelpers.kt @@ -9,6 +9,7 @@ import it.unimi.dsi.fastutil.ints.IntIterator import it.unimi.dsi.fastutil.ints.IntList import it.unimi.dsi.fastutil.ints.IntOpenHashSet import it.unimi.dsi.fastutil.ints.IntSet +import it.unimi.dsi.fastutil.ints.IntSortedSet import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import it.unimi.dsi.fastutil.objects.ObjectArrayList @@ -22,6 +23,7 @@ import ru.dbotthepony.mc.otm.container.util.ItemStackHashStrategy import ru.dbotthepony.mc.otm.container.util.containerSlot import ru.dbotthepony.mc.otm.container.util.slotIterator import ru.dbotthepony.mc.otm.core.addAll +import ru.dbotthepony.mc.otm.core.collect.IntRange2Set import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.collect.toList import ru.dbotthepony.mc.otm.core.isNotEmpty @@ -38,89 +40,12 @@ inline operator fun Container.get(index: Int): ItemStack = getItem(index) @Suppress("nothing_to_inline") inline operator fun IFluidHandler.get(index: Int) = getFluidInTank(index) -val Container.slotRange: IntIterable get() { - return IntIterable { - val i = (0 until containerSize).iterator() - - object : IntIterator { - override fun hasNext(): Boolean { - return i.hasNext() - } - - override fun remove() { - throw UnsupportedOperationException() - } - - override fun nextInt(): Int { - return i.nextInt() - } - } - } +val Container.slotRange: IntRange2Set get() { + return IntRange2Set.openEnded(0, containerSize) } -fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange): ItemStack { - if (this is IMatteryContainer) { - return this.addItem(stack, simulate, slots) - } - - if (stack.isEmpty) - return stack - - val copy = stack.copy() - - // двигаем в одинаковые слоты - var i = slots.intIterator() - - while (i.hasNext()) { - val slot = i.nextInt() - - if (ItemStack.isSameItemSameComponents(this[slot], copy)) { - val slotStack = this[slot] - val slotLimit = maxStackSize.coerceAtMost(slotStack.maxStackSize) - - if (slotStack.count < slotLimit) { - val newCount = (slotStack.count + copy.count).coerceAtMost(slotLimit) - val diff = newCount - slotStack.count - - if (!simulate) { - slotStack.count = newCount - setChanged() - } - - copy.shrink(diff) - - if (copy.isEmpty) { - return copy - } - } - } - } - - // двигаем в пустые слоты - i = slots.intIterator() - - while (i.hasNext()) { - val slot = i.nextInt() - - if (this[slot].isEmpty) { - val diff = copy.count.coerceAtMost(maxStackSize.coerceAtMost(copy.maxStackSize)) - - if (!simulate) { - val copyToPut = copy.copy() - copyToPut.count = diff - this[slot] = copyToPut - setChanged() - } - - copy.shrink(diff) - - if (copy.isEmpty) { - return copy - } - } - } - - return copy +fun Container.addItem(stack: ItemStack, simulate: Boolean, slots: IntSet = slotRange): ItemStack { + return IEnhancedContainer.wrap(this).addItem(stack, simulate, slots) } fun Container.vanishCursedItems() { @@ -131,7 +56,7 @@ fun Container.vanishCursedItems() { } } -fun Container.balance(slots: IntSet, checkForEmpty: Boolean = true) { +fun Container.balance(slots: IntCollection, checkForEmpty: Boolean = true) { if (slots.isEmpty() || checkForEmpty && !slots.any { getItem(it).isNotEmpty }) return val empty = IntArrayList() @@ -312,12 +237,18 @@ fun Container.sortWithIndices(sortedSlots: IntCollection) { if (value in 0 until containerSize && seen.add(value)) { val slot = containerSlot(value) - if ( - slot.isNotEmpty && - !slot.isForbiddenForAutomation && - slot.item.count <= slot.getMaxStackSize() && - (!slot.hasFilter || slot.getFilter() != slot.item.item || slot.getMaxStackSize() > 1) - ) { + val condition: Boolean + + if (slot is IFilteredContainerSlot) { + condition = slot.isNotEmpty && + !slot.filter.denyAll && + slot.item.count <= slot.maxStackSize(slot.item) && + (slot.filter.allowAll || !slot.filter.test(slot.item) || slot.maxStackSize(slot.item) > 1) + } else { + condition = slot.isNotEmpty && slot.item.count <= slot.maxStackSize(slot.item) + } + + if (condition) { valid.add(slot) } } @@ -335,21 +266,24 @@ fun Container.computeSortedIndices(comparator: Comparator = ItemStack if (isEmpty) return IntList.of() - val slots = slotIterator().filter { !it.isForbiddenForAutomation && it.getMaxStackSize() >= it.item.count }.toList() + val slots = slotIterator() + .withIndex() + .filter { (_, it) -> (it !is IFilteredContainerSlot || !it.filter.denyAll) && it.maxStackSize(it.item) >= it.item.count } + .toList() if (slots.isEmpty()) return IntList.of() val items = Object2ObjectOpenCustomHashMap>(ItemStackHashStrategy) - slots.forEach { + slots.forEach { (index, it) -> val get = items[it.item] if (get == null) { - items[it.item] = it.item.copy() to IntArrayList().also { s -> s.add(it.slot) } + items[it.item] = it.item.copy() to IntArrayList().also { s -> s.add(index) } } else { get.first.count += it.item.count - get.second.add(it.slot) + get.second.add(index) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/DynamicallyProxiedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/DynamicallyProxiedContainer.kt deleted file mode 100644 index b2e400392..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/DynamicallyProxiedContainer.kt +++ /dev/null @@ -1,81 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import net.minecraft.world.Container -import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.Item -import net.minecraft.world.item.ItemStack -import java.util.function.Predicate -import java.util.function.Supplier - -/** - * because mods tend to do crazy shit - */ -class DynamicallyProxiedContainer(private val toProxy: Supplier) : IContainer { - override fun clearContent() { - return toProxy.get().clearContent() - } - - override fun getContainerSize(): Int { - return toProxy.get().containerSize - } - - override fun isEmpty(): Boolean { - return toProxy.get().isEmpty - } - - override fun getItem(slot: Int): ItemStack { - return toProxy.get().getItem(slot) - } - - override fun removeItem(slot: Int, amount: Int): ItemStack { - return toProxy.get().removeItem(slot, amount) - } - - override fun removeItemNoUpdate(slot: Int): ItemStack { - return toProxy.get().removeItemNoUpdate(slot) - } - - override fun setItem(slot: Int, itemStack: ItemStack) { - return toProxy.get().setItem(slot, itemStack) - } - - override fun setChanged() { - return toProxy.get().setChanged() - } - - override fun stillValid(player: Player): Boolean { - return toProxy.get().stillValid(player) - } - - override fun getMaxStackSize(): Int { - return toProxy.get().getMaxStackSize() - } - - override fun startOpen(player: Player) { - toProxy.get().startOpen(player) - } - - override fun stopOpen(player: Player) { - toProxy.get().stopOpen(player) - } - - override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { - return toProxy.get().canPlaceItem(slot, itemStack) - } - - override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean { - return toProxy.get().canTakeItem(container, slot, itemStack) - } - - override fun countItem(item: Item): Int { - return toProxy.get().countItem(item) - } - - override fun hasAnyOf(items: Set): Boolean { - return toProxy.get().hasAnyOf(items) - } - - override fun hasAnyMatching(predicate: Predicate): Boolean { - return toProxy.get().hasAnyMatching(predicate) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/EnhancedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/EnhancedContainer.kt new file mode 100644 index 000000000..630016b90 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/EnhancedContainer.kt @@ -0,0 +1,212 @@ +package ru.dbotthepony.mc.otm.container + +import it.unimi.dsi.fastutil.ints.IntCollection +import it.unimi.dsi.fastutil.ints.IntOpenHashSet +import net.minecraft.core.HolderLookup.Provider +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.Tag +import net.minecraft.resources.RegistryOps +import net.minecraft.world.SimpleContainer +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import net.neoforged.neoforge.common.util.INBTSerializable +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kommons.collect.iterateClearBits +import ru.dbotthepony.kommons.collect.iterateSetBits +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.core.collect.IntRange2Set +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.nbt.set +import java.util.BitSet + +/** + * Flexible base implementation of [IEnhancedContainer], designed to be inherited, or used as-is + * if no specific logic is required (this implementation is more efficient than one provided by [SlottedContainer.simple]). + * + * This is supposed to be counterpart to [SimpleContainer] of Minecraft itself, with more features + * and improved performance (inside [IEnhancedContainer] defined methods). + */ +abstract class EnhancedContainer(private val size: Int) : BitmapTrackingContainer(), INBTSerializable { + private val items = Array(size) { ItemStack.EMPTY } + private val observedItems = Array(size) { ItemStack.EMPTY } + + protected open fun notifySlotChanged(slot: Int, old: ItemStack) {} + + private fun observeSlotChanges(slot: Int) { + if (items[slot].count != observedItems[slot].count || !ItemStack.isSameItemSameComponents(items[slot], observedItems[slot])) { + notifySlotChanged(slot, observedItems[slot]) + + if (items[slot].isEmpty) { + items[slot] = ItemStack.EMPTY + observedItems[slot] = ItemStack.EMPTY + bitmap[slot] = false + } else { + notifySlotChanged(slot, observedItems[slot]) + observedItems[slot] = items[slot].copy() + bitmap[slot] = true + } + } + } + + override fun setChanged(slot: Int) { + observeSlotChanges(slot) + } + + override fun clearContent() { + items.fill(ItemStack.EMPTY) + observedItems.fill(ItemStack.EMPTY) + bitmap.clear() + } + + override fun setChanged() { + for (slot in 0 until size) + observeSlotChanges(slot) + } + + final override fun getContainerSize(): Int { + return size + } + + final override fun getItem(slot: Int): ItemStack { + return items[slot] + } + + final override fun removeItem(slot: Int, amount: Int): ItemStack { + val item = items[slot] + + if (item.isEmpty) + return ItemStack.EMPTY + else if (item.count >= amount) + return removeItemNoUpdate(slot) + else { + val split = item.split(amount) + notifySlotChanged(slot, observedItems[slot]) + observedItems[slot] = item.copy() + return split + } + } + + final override fun removeItemNoUpdate(slot: Int): ItemStack { + val item = items[slot] + + if (item.isEmpty) + return ItemStack.EMPTY + + items[slot] = ItemStack.EMPTY + notifySlotChanged(slot, observedItems[slot]) + observedItems[slot] = ItemStack.EMPTY + bitmap[slot] = false + + return item + } + + final override fun setItem(slot: Int, itemStack: ItemStack) { + if (itemStack.count != items[slot].count || !ItemStack.isSameItemSameComponents(itemStack, items[slot])) { + items[slot] = itemStack + notifySlotChanged(slot, observedItems[slot]) + observedItems[slot] = itemStack.copy() + bitmap[slot] = itemStack.isNotEmpty + } + } + + override fun stillValid(player: Player): Boolean { + return true + } + + /** + * Called from inside [serializeNBT] per slot, return true if you have written something into [nbt]. + * If returning false, and slot does not contain an item, tag entry is discarded + */ + protected open fun attachSlotData(provider: Provider, slot: Int, nbt: CompoundTag, ops: RegistryOps): Boolean { + return false + } + + protected open fun loadSlotData(provider: Provider, slot: Int, nbt: CompoundTag, ops: RegistryOps) { + + } + + override fun serializeNBT(provider: Provider): CompoundTag { + val ops = provider.createSerializationContext(NbtOps.INSTANCE) + + return CompoundTag().also { + it["items"] = ListTag().also { + for (i in 0 until size) { + val tag = CompoundTag() + var attached = attachSlotData(provider, i, tag, ops) + + if (items[i].isNotEmpty) { + attached = true + tag["item"] = ItemStack.OPTIONAL_CODEC.encodeStart(ops, items[i]) + .getOrThrow { RuntimeException("Unable to serialize item ${items[i]} at slot $i: $it") } + } + + tag["slot"] = i + + if (attached) { + it.add(tag) + } + } + } + } + } + + override fun deserializeNBT(provider: Provider, nbt: CompoundTag) { + val copy = observedItems.copyOf() + items.fill(ItemStack.EMPTY) + observedItems.fill(ItemStack.EMPTY) + bitmap.clear() + + val seenSlots = IntOpenHashSet() + val ops = provider.createSerializationContext(NbtOps.INSTANCE) + + if ("items" in nbt) { + for (element in nbt.getList("items", Tag.TAG_COMPOUND.toInt())) { + element as CompoundTag + + if ("slot" !in element) continue + val slot = element.getInt("slot") + if (!seenSlots.add(slot)) continue + + if ("item" in element) { + ItemStack.OPTIONAL_CODEC.decode(ops, element["item"]) + .map { it.first } + .ifError { LOGGER.error("Failed to deserialize item stack in slot $slot: ${it.message()}") } + .ifSuccess { + if (it.isNotEmpty) { + items[slot] = it + observedItems[slot] = it.copy() + bitmap[slot] = true + + if (it.count != copy[slot].count || !ItemStack.isSameItemSameComponents(it, copy[slot])) + notifySlotChanged(slot, copy[slot]) + } + } + } + + loadSlotData(provider, slot, element, ops) + } + } + } + + open class Simple(size: Int) : EnhancedContainer(size) { + private val slots by lazy(LazyThreadSafetyMode.PUBLICATION) { Array(size) { IContainerSlot.Simple(it, this) } } + + override fun containerSlot(slot: Int): IContainerSlot { + return slots[slot] + } + } + + open class WithListener(size: Int, private val listener: Runnable) : Simple(size) { + override fun notifySlotChanged(slot: Int, old: ItemStack) { + super.notifySlotChanged(slot, old) + listener.run() + } + } + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt deleted file mode 100644 index dbe17de88..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/HandlerFilter.kt +++ /dev/null @@ -1,153 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import net.minecraft.world.item.ItemStack -import net.neoforged.neoforge.capabilities.Capabilities -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.capability.fluid.stream -import ru.dbotthepony.mc.otm.core.isNotEmpty -import ru.dbotthepony.mc.otm.core.math.Decimal - -interface HandlerFilter { - fun canInsert(slot: Int, stack: ItemStack): Boolean { - return true - } - - fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return true - } - - fun preInsert(slot: Int, stack: ItemStack, simulate: Boolean) {} - fun preExtract(slot: Int, amount: Int, simulate: Boolean) {} - - fun modifyInsertCount(slot: Int, stack: ItemStack, existing: ItemStack, simulate: Boolean): Int { - return stack.count - } - - fun modifyExtractCount(slot: Int, amount: Int, simulate: Boolean): Int { - return amount - } - - fun and(other: HandlerFilter): HandlerFilter { - return object : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return this@HandlerFilter.canInsert(slot, stack) && other.canInsert(slot, stack) - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return this@HandlerFilter.canExtract(slot, amount, stack) && other.canExtract(slot, amount, stack) - } - - override fun preInsert(slot: Int, stack: ItemStack, simulate: Boolean) { - this@HandlerFilter.preInsert(slot, stack, simulate) - other.preInsert(slot, stack, simulate) - } - - override fun preExtract(slot: Int, amount: Int, simulate: Boolean) { - this@HandlerFilter.preExtract(slot, amount, simulate) - other.preExtract(slot, amount, simulate) - } - } - } - - object FluidContainers : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.tanks > 0 } ?: false - } - } - - object DrainableFluidContainers : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.stream().anyMatch { it.isNotEmpty } } ?: false - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return !canInsert(slot, stack) - } - } - - object OnlyIn : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return true - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return false - } - } - - object OnlyOut : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return false - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return true - } - } - - object Both : HandlerFilter - - object Dischargeable : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { it.canExtract() && it.extractEnergy(Int.MAX_VALUE, true) > 0 } ?: false - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return stack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { !it.canExtract() || it.extractEnergy(Int.MAX_VALUE, true) <= 0 } ?: false - } - } - - object Chargeable : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { it.canReceive() && it.receiveEnergy(Int.MAX_VALUE, true) > 0 } ?: false - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return stack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { !it.canReceive() || it.receiveEnergy(Int.MAX_VALUE, true) <= 0 } ?: false - } - } - - object ChemicalFuel : HandlerFilter { - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return stack.getBurnTime(null) <= 0 - } - - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getBurnTime(null) > 0 - } - } - - object IsPattern : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getCapability(MatteryCapability.PATTERN_ITEM) != null - } - } - - object MatterProviders : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getCapability(MatteryCapability.MATTER_ITEM) - ?.let { it.matterFlow.output && it.extractMatterChecked(Decimal.POSITIVE_INFINITY, true) > Decimal.ZERO } - ?: false - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return stack.getCapability(MatteryCapability.MATTER_ITEM) - ?.let { !it.matterFlow.output || it.extractMatterChecked(Decimal.POSITIVE_INFINITY, true) <= Decimal.ZERO } - ?: false - } - } - - object MatterConsumers : HandlerFilter { - override fun canInsert(slot: Int, stack: ItemStack): Boolean { - return stack.getCapability(MatteryCapability.MATTER_ITEM) - ?.let { it.matterFlow.input && it.receiveMatterChecked(Decimal.POSITIVE_INFINITY, true) > Decimal.ZERO } - ?: false - } - - override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean { - return stack.getCapability(MatteryCapability.MATTER_ITEM) - ?.let { !it.matterFlow.input || it.receiveMatterChecked(Decimal.POSITIVE_INFINITY, true) <= Decimal.ZERO } - ?: false - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IAutomatedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IAutomatedContainer.kt new file mode 100644 index 000000000..529512d22 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IAutomatedContainer.kt @@ -0,0 +1,44 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.Container +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack +import net.neoforged.neoforge.items.IItemHandler +import net.neoforged.neoforge.items.IItemHandlerModifiable + +/** + * Reinforced [ISlottedContainer] which slots are [IAutomatedContainerSlot]s, which + * subsequently allow this container to implement [IItemHandler] + */ +interface IAutomatedContainer : ISlottedContainer, IItemHandlerModifiable { + override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { + return containerSlot(slot).canAutomationPlaceItem(itemStack) + } + + override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean { + return containerSlot(slot).canAutomationTakeItem() + } + + override fun getSlots() = containerSize + override fun getStackInSlot(slot: Int) = containerSlot(slot).item + + override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack { + return containerSlot(slot).insertItem(stack, simulate) + } + + override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack { + return containerSlot(slot).extractItem(amount, simulate) + } + + override fun getSlotLimit(slot: Int): Int { + return containerSlot(slot).maxStackSize + } + + override fun setStackInSlot(slot: Int, stack: ItemStack) { + setItem(slot, stack) + } + + override fun isItemValid(slot: Int, stack: ItemStack): Boolean { + return canPlaceItem(slot, stack) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IAutomatedContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IAutomatedContainerSlot.kt new file mode 100644 index 000000000..2c7df2085 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IAutomatedContainerSlot.kt @@ -0,0 +1,93 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.item.ItemStack +import net.neoforged.neoforge.items.IItemHandler + +/** + * Slot of [IAutomatedContainer], with additional methods to implement interaction behavior for both for players and mechanisms + */ +interface IAutomatedContainerSlot : IContainerSlot { + fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return true + } + + fun canAutomationTakeItem(desired: Int = item.count): Boolean { + return true + } + + fun modifyAutomationPlaceCount(itemStack: ItemStack): Int { + return itemStack.count + } + + fun modifyAutomationExtractionCount(desired: Int): Int { + return desired + } + + /** + * Slot-specific implementation for [IItemHandler.insertItem] + */ + fun insertItem(stack: ItemStack, simulate: Boolean): ItemStack { + if (!canAutomationPlaceItem(stack)) + return stack + + var amount = modifyAutomationPlaceCount(stack) + + if (amount <= 0) + return stack + + if (item.isEmpty) { + amount = stack.count.coerceAtMost(maxStackSize(stack)).coerceAtMost(amount) + + if (!simulate) { + item = stack.copyWithCount(amount) + } + + if (stack.count <= amount) { + return ItemStack.EMPTY + } else { + return stack.copyWithCount(stack.count - amount) + } + } else if (item.isStackable && maxStackSize(item) > item.count && ItemStack.isSameItemSameComponents(item, stack)) { + val newCount = maxStackSize(item).coerceAtMost(item.count + stack.count.coerceAtMost(amount)) + val diff = newCount - item.count + + if (diff != 0) { + if (!simulate) { + item.grow(diff) + setChanged() + } + + val copy = stack.copy() + copy.shrink(diff) + return copy + } + } + + return stack + } + + fun extractItem(amount: Int, simulate: Boolean): ItemStack { + if (amount <= 0 || !canAutomationTakeItem(amount) || item.isEmpty) + return ItemStack.EMPTY + + @Suppress("name_shadowing") + val amount = modifyAutomationExtractionCount(amount) + if (amount <= 0 || !canAutomationTakeItem(amount)) return ItemStack.EMPTY + + val minimal = amount.coerceAtMost(item.count) + val copy = item.copy() + copy.count = minimal + + if (!simulate) { + if (item.count == minimal) { + item = ItemStack.EMPTY + } else { + item.shrink(minimal) + } + + setChanged() + } + + return copy + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainer.kt deleted file mode 100644 index 3afa4ab72..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainer.kt +++ /dev/null @@ -1,95 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import net.minecraft.world.Container -import net.minecraft.world.entity.player.Player -import net.minecraft.world.item.Item -import net.minecraft.world.item.ItemStack -import java.util.function.Predicate - -// passthrough all default methods to fix Kotlin bug related to implementation delegation not properly working on Java interfaces -// and also to give params proper names -// https://youtrack.jetbrains.com/issue/KT-55080/Change-the-behavior-of-inheritance-delegation-to-delegates-implementing-Java-interfaces-with-default-methods -interface IContainer : Container { - override fun getMaxStackSize(): Int { - return super.getMaxStackSize() - } - - override fun startOpen(player: Player) { - super.startOpen(player) - } - - override fun stopOpen(player: Player) { - super.stopOpen(player) - } - - override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { - return super.canPlaceItem(slot, itemStack) - } - - override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean { - return super.canTakeItem(container, slot, itemStack) - } - - override fun countItem(item: Item): Int { - return super.countItem(item) - } - - override fun hasAnyOf(items: Set): Boolean { - return super.hasAnyOf(items) - } - - override fun hasAnyMatching(predicate: Predicate): Boolean { - return super.hasAnyMatching(predicate) - } - - override fun clearContent() - override fun getContainerSize(): Int - override fun isEmpty(): Boolean - override fun getItem(slot: Int): ItemStack - override fun removeItem(slot: Int, amount: Int): ItemStack - override fun removeItemNoUpdate(slot: Int): ItemStack - override fun setItem(slot: Int, itemStack: ItemStack) - override fun setChanged() - - override fun stillValid(player: Player): Boolean - companion object { - fun wrap(container: Container): IContainer { - if (container is IContainer) - return container - else - return object : IContainer, Container by container { - override fun getMaxStackSize(): Int { - return container.getMaxStackSize() - } - - override fun startOpen(player: Player) { - container.startOpen(player) - } - - override fun stopOpen(player: Player) { - container.stopOpen(player) - } - - override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { - return container.canPlaceItem(slot, itemStack) - } - - override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean { - return container.canTakeItem(container, slot, itemStack) - } - - override fun countItem(item: Item): Int { - return container.countItem(item) - } - - override fun hasAnyOf(items: Set): Boolean { - return container.hasAnyOf(items) - } - - override fun hasAnyMatching(predicate: Predicate): Boolean { - return container.hasAnyMatching(predicate) - } - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainerSlot.kt index ee1ccfd22..f5f8e26d2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainerSlot.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IContainerSlot.kt @@ -1,9 +1,7 @@ package ru.dbotthepony.mc.otm.container import net.minecraft.world.Container -import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.mc.otm.core.isNotEmpty @@ -12,57 +10,64 @@ import ru.dbotthepony.mc.otm.core.isNotEmpty * for Player interaction. */ interface IContainerSlot : Delegate { - val slot: Int - val container: Container - - operator fun component1() = slot - operator fun component2() = item - - fun getMaxStackSize(item: ItemStack = this.item): Int { - return container.maxStackSize - } - - val isForbiddenForAutomation: Boolean get() { - return getFilter() === Items.AIR - } - - fun getFilter(): Item? + fun setChanged() + var item: ItemStack /** - * @return whenever the filter was set. Returns false only if container can't be filtered. + * Max stack size regardless of item + * + * Prefer to use ItemStack version instead */ - fun setFilter(filter: Item? = null): Boolean + val maxStackSize: Int - val hasFilter: Boolean - get() = getFilter() != null - - fun remove() { - container[slot] = ItemStack.EMPTY + /** + * Max amount of [item] that can be stored in this slot. + * + * This may be larger or smaller than value returned [ItemStack.getMaxStackSize], + * and as such returned value by this method should be ground truth + */ + fun maxStackSize(item: ItemStack): Int { + return maxStackSize.coerceAtMost(item.maxStackSize) } - fun remove(count: Int): ItemStack { - return container.removeItem(slot, count) - } + fun remove(): ItemStack + fun remove(count: Int): ItemStack override fun get(): ItemStack { - return container[slot] + return item } override fun accept(t: ItemStack) { - container[slot] = t + item = t } - fun setChanged() { - container.setChanged() - } - - var item: ItemStack - get() = container[slot] - set(value) { container[slot] = value } - val isEmpty: Boolean - get() = container[slot].isEmpty + get() = item.isEmpty val isNotEmpty: Boolean - get() = container[slot].isNotEmpty + get() = item.isNotEmpty + + open class Simple(protected val slot: Int, protected val container: Container) : IContainerSlot { + override fun setChanged() { + container.setChanged() + } + + override var item: ItemStack + get() = container[slot] + set(value) { container[slot] = value } + override val maxStackSize: Int + get() = container.maxStackSize + + override fun remove(count: Int): ItemStack { + return container.removeItem(slot, count) + } + + override fun remove(): ItemStack { + return container.removeItemNoUpdate(slot) + } + + override fun maxStackSize(item: ItemStack): Int { + return maxStackSize.coerceAtMost(item.maxStackSize) + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt new file mode 100644 index 000000000..c201a4bb8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedContainer.kt @@ -0,0 +1,477 @@ +package ru.dbotthepony.mc.otm.container + +import it.unimi.dsi.fastutil.ints.IntCollection +import net.minecraft.world.Container +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.ItemStack +import net.minecraft.world.item.Items +import net.minecraft.world.item.crafting.RecipeInput +import ru.dbotthepony.mc.otm.core.collect.any +import ru.dbotthepony.mc.otm.core.collect.filter +import ru.dbotthepony.mc.otm.core.collect.map +import ru.dbotthepony.mc.otm.core.isNotEmpty +import java.util.function.Predicate +import java.util.stream.Stream +import java.util.stream.StreamSupport + +/** + * "Backward-compatible" enhanced container interface, where all methods can be derived/emulated from existing [Container] code + * + * This is useful because it allows to interact with actually enhanced and regular containers through unified interface, + * and actual implementations of this interface are likely to provide efficient method implementations in place of derived/emulated ones. + */ +interface IEnhancedContainer : Container, RecipeInput, Iterable, StackedContentsCompatible { + // https://youtrack.jetbrains.com/issue/KT-55080/Change-the-behavior-of-inheritance-delegation-to-delegates-implementing-Java-interfaces-with-default-methods + override fun getMaxStackSize(): Int { + return super.getMaxStackSize() + } + + override fun startOpen(player: Player) { + super.startOpen(player) + } + + override fun stopOpen(player: Player) { + super.stopOpen(player) + } + + override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { + return super.canPlaceItem(slot, itemStack) + } + + override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean { + return super.canTakeItem(container, slot, itemStack) + } + + override fun clearContent() + override fun getContainerSize(): Int + override fun getItem(slot: Int): ItemStack + override fun removeItem(slot: Int, amount: Int): ItemStack + override fun removeItemNoUpdate(slot: Int): ItemStack + override fun setItem(slot: Int, itemStack: ItemStack) + override fun setChanged() + + // provide non-ambiguous get and set operators + operator fun get(slot: Int): ItemStack { + return getItem(slot) + } + + operator fun set(slot: Int, value: ItemStack) { + setItem(slot, value) + } + + fun containerSlot(slot: Int): S + + override fun fillStackedContents(contents: StackedContents) { + forEach { contents.accountStack(it) } + } + + /** + * Returns iterator over **all** slots this container has + */ + fun slotIterator(): Iterator { + return (0 until containerSize).iterator().map { containerSlot(it) } + } + + fun nonEmptySlotIterator(): Iterator { + return nonEmptySlotIndexIterator().map { containerSlot(it) } + } + + fun emptySlotIterator(): Iterator { + return emptySlotIndexIterator().map { containerSlot(it) } + } + + fun nonEmptySlotIterator(allowedSlots: IntCollection): Iterator { + return nonEmptySlotIndexIterator(allowedSlots).map { containerSlot(it) } + } + + fun emptySlotIterator(allowedSlots: IntCollection): Iterator { + return emptySlotIndexIterator(allowedSlots).map { containerSlot(it) } + } + + private fun slotIndexWalker(allowedSlots: IntCollection, predicate: Predicate): IntIterator { + return object : IntIterator() { + private val parent = allowedSlots.intIterator() + private var foundNext = false + private var next = -1 + + private fun findNext() { + if (!foundNext) { + foundNext = true + next = -1 + + while (parent.hasNext()) { + val i = parent.nextInt() + + if (predicate.test(this@IEnhancedContainer[i])) { + next = i + break + } + } + } + } + + override fun nextInt(): Int { + findNext() + + if (next == -1) + throw NoSuchElementException() + + foundNext = false + val next = next + this.next = -1 + return next + } + + override fun hasNext(): Boolean { + findNext() + return next != -1 + } + } + } + + fun emptySlotIndexIterator(allowedSlots: IntCollection): IntIterator { + return slotIndexWalker(allowedSlots) { it.isEmpty } + } + + fun nonEmptySlotIndexIterator(allowedSlots: IntCollection): IntIterator { + return slotIndexWalker(allowedSlots) { it.isNotEmpty } + } + + fun emptySlotIndexIterator(): IntIterator { + return emptySlotIndexIterator(slotRange) + } + + fun nonEmptySlotIndexIterator(): IntIterator { + return nonEmptySlotIndexIterator(slotRange) + } + + fun slotIndexWithItemIterator(item: Item): IntIterator { + return slotIndexWithItemIterator(item, slotRange) + } + + fun slotIndexWithItemIterator(item: Item, allowedSlots: IntCollection): IntIterator { + val parent = nonEmptySlotIndexIterator(allowedSlots).filter { this[it].isNotEmpty && this[it].item === item } + + return object : IntIterator() { + override fun nextInt(): Int { + return parent.next() + } + + override fun hasNext(): Boolean { + return parent.hasNext() + } + } + } + + fun slotWithItemIterator(item: Item): Iterator { + return slotWithItemIterator(item, slotRange) + } + + fun slotWithItemIterator(item: Item, allowedSlots: IntCollection): Iterator { + return slotIndexWithItemIterator(item, allowedSlots).map { containerSlot(it) } + } + + fun nextEmptySlot(startIndex: Int): Int { + for (i in startIndex until containerSize) { + if (this[i].isEmpty) { + return i + } + } + + return -1 + } + + fun nextNonEmptySlot(startIndex: Int): Int { + for (i in startIndex until containerSize) { + if (this[i].isNotEmpty) { + return i + } + } + + return -1 + } + + fun nextSlotWithItem(startIndex: Int, item: Item): Int { + var find = nextNonEmptySlot(startIndex) + + while (find != -1 && this[find].item !== item) + find = nextNonEmptySlot(find + 1) + + return find + } + + fun setChanged(slot: Int) { + return setChanged() + } + + fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { + return maxStackSize + } + + /** + * Returns iterator over **non-empty** [ItemStack]s inside this container + */ + override fun iterator(): Iterator { + return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty } + } + + override fun isEmpty(): Boolean { + return nextNonEmptySlot(0) != -1 + } + + val hasEmptySlots: Boolean get() { + return nextEmptySlot(0) != -1 + } + + override fun size(): Int { + return containerSize + } + + override fun countItem(item: Item): Int { + var count = 0 + + for (slot in slotWithItemIterator(item)) + count += slot.item.count + + return count + } + + override fun hasAnyOf(items: Set): Boolean { + if (Items.AIR in items && hasEmptySlots) + return true + + return iterator().any { it.item in items } + } + + override fun hasAnyMatching(predicate: Predicate): Boolean { + if (predicate.test(ItemStack.EMPTY) && hasEmptySlots) + return true + + return iterator().any(predicate) + } + + fun toList(): MutableList { + val list = ArrayList(containerSize) + + for (i in 0 until containerSize) { + list.add(this[i]) + } + + return list + } + + fun copyToList(): MutableList { + val list = ArrayList(containerSize) + + for (i in 0 until containerSize) { + list.add(this[i].copy()) + } + + return list + } + + private fun addItem(stack: ItemStack, simulate: Boolean, filterPass: Boolean, slots: IntCollection, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack { + if (stack.isEmpty || slots.isEmpty()) + return stack + + // двигаем в одинаковые слоты + for (slot in slotWithItemIterator(stack.item, slots)) { + val condition: Boolean + + if (slot is IFilteredContainerSlot) { + condition = (ignoreFilters || !slot.filter.denyAll) && + ItemStack.isSameItemSameComponents(slot.item, stack) && + (ignoreFilters || !filterPass && slot.filter.allowAll || filterPass && !slot.filter.allowAll && slot.filter.test(stack)) + } else { + condition = (ignoreFilters || !filterPass) && ItemStack.isSameItemSameComponents(slot.item, stack) + } + + if (condition) { + val slotLimit = slot.maxStackSize(slot.item) + + if (slot.item.count < slotLimit) { + val newCount = (slot.item.count + stack.count).coerceAtMost(slotLimit) + val diff = newCount - slot.item.count + + if (!simulate) { + slot.item.count = newCount + slot.setChanged() + + if (popTime != null) { + slot.item.popTime = popTime + } + } + + stack.shrink(diff) + + if (stack.isEmpty) + return ItemStack.EMPTY + } + } + } + + if (!onlyIntoExisting) { + for (slot in emptySlotIterator(slots)) { + val condition: Boolean + + if (slot is IFilteredContainerSlot) { + condition = (ignoreFilters || !slot.filter.denyAll) && + (ignoreFilters || !filterPass && slot.filter.allowAll || filterPass && !slot.filter.allowAll && slot.filter.test(stack)) + } else { + condition = ignoreFilters || !filterPass + } + + if (condition) { + val diff = stack.count.coerceAtMost(slot.maxStackSize(stack)) + + if (!simulate) { + val copyToPut = stack.copy() + copyToPut.count = diff + slot.item = copyToPut + + if (popTime != null) { + copyToPut.popTime = popTime + } + } + + stack.shrink(diff) + + if (stack.isEmpty) + return ItemStack.EMPTY + } + } + } + + return stack + } + + /** + * Hint used internally by [IEnhancedContainer] to potentially speed up default method implementations + */ + val hasFilterableSlots: Boolean + get() = false + + fun addItem(stack: ItemStack, simulate: Boolean, slots: IntCollection = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): ItemStack { + if (stack.isEmpty || slots.isEmpty()) + return stack + + if (ignoreFilters || !hasFilterableSlots) { + return addItem(stack.copy(), simulate, filterPass = true, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = true) + } else { + var copy = addItem(stack.copy(), simulate, filterPass = true, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = false) + copy = addItem(copy, simulate, filterPass = false, slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = false) + return copy + } + } + + /** + * Unlike [addItem], modifies original [stack] + * + * @return Whenever [stack] was modified + */ + fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntCollection = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): Boolean { + if (stack.isEmpty) + return false + + val result = addItem(stack, simulate = simulate, slots = slots, onlyIntoExisting = onlyIntoExisting, popTime = popTime, ignoreFilters = ignoreFilters) + if (!simulate) stack.count = result.count + return result.count != stack.count + } + + fun fullyAddItem(stack: ItemStack, slots: IntCollection = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): Boolean { + if (!addItem(stack, simulate = true, slots = slots, popTime = popTime, onlyIntoExisting = onlyIntoExisting, ignoreFilters = ignoreFilters).isEmpty) + return false + + return addItem(stack, simulate = false, slots = slots, popTime = popTime, onlyIntoExisting = onlyIntoExisting, ignoreFilters = ignoreFilters).isEmpty + } + + fun stream(): Stream { + return StreamSupport.stream(spliterator(), false) + } + + private class Wrapper(private val parent: Container) : IEnhancedContainer { + override fun containerSlot(slot: Int): IContainerSlot { + return IContainerSlot.Simple(slot, parent) + } + + override fun clearContent() { + return parent.clearContent() + } + + override fun setChanged() { + return parent.setChanged() + } + + override fun getContainerSize(): Int { + return parent.containerSize + } + + override fun getItem(slot: Int): ItemStack { + return parent.getItem(slot) + } + + override fun removeItem(slot: Int, amount: Int): ItemStack { + return parent.removeItem(slot, amount) + } + + override fun removeItemNoUpdate(slot: Int): ItemStack { + return parent.removeItemNoUpdate(slot) + } + + override fun setItem(slot: Int, itemStack: ItemStack) { + return parent.setItem(slot, itemStack) + } + + override fun stillValid(player: Player): Boolean { + return parent.stillValid(player) + } + + override fun getMaxStackSize(): Int { + return parent.maxStackSize + } + + override fun startOpen(player: Player) { + parent.startOpen(player) + } + + override fun stopOpen(player: Player) { + parent.stopOpen(player) + } + + override fun canPlaceItem(slot: Int, itemStack: ItemStack): Boolean { + return parent.canPlaceItem(slot, itemStack) + } + + override fun canTakeItem(container: Container, slot: Int, itemStack: ItemStack): Boolean { + return parent.canTakeItem(container, slot, itemStack) + } + + override fun isEmpty(): Boolean { + return parent.isEmpty + } + + override fun hasAnyMatching(predicate: Predicate): Boolean { + return parent.hasAnyMatching(predicate) + } + + override fun hasAnyOf(items: Set): Boolean { + return parent.hasAnyOf(items) + } + + override fun countItem(item: Item): Int { + return parent.countItem(item) + } + } + + companion object { + fun wrap(other: Container): IEnhancedContainer<*> { + if (other is IEnhancedContainer<*>) + return other + + return Wrapper(other) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedCraftingContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedCraftingContainer.kt new file mode 100644 index 000000000..0866e8172 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IEnhancedCraftingContainer.kt @@ -0,0 +1,33 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.entity.player.StackedContents +import net.minecraft.world.inventory.CraftingContainer +import net.minecraft.world.item.ItemStack + +interface IEnhancedCraftingContainer : IEnhancedContainer, CraftingContainer { + override fun getItems(): MutableList { + return toList() + } + + override fun fillStackedContents(contents: StackedContents) { + forEach { contents.accountSimpleStack(it) } + } + + class Wrapper, out S : IContainerSlot>(val parent: C, private val width: Int, private val height: Int) : IEnhancedCraftingContainer, IEnhancedContainer by parent { + init { + require(width * height == parent.containerSize) { "Crafting container dimensions ($width x $height) do not match container size provided (${parent.containerSize})" } + } + + override fun getWidth(): Int { + return width + } + + override fun getHeight(): Int { + return height + } + + override fun fillStackedContents(contents: StackedContents) { + super.fillStackedContents(contents) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredAutomatedContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredAutomatedContainerSlot.kt new file mode 100644 index 000000000..4ee9144a0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredAutomatedContainerSlot.kt @@ -0,0 +1,14 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items + +interface IFilteredAutomatedContainerSlot : IFilteredContainerSlot, IAutomatedContainerSlot { + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && filter.test(itemStack) + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return super.canAutomationTakeItem(desired) && !filter.denyAll + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt new file mode 100644 index 000000000..6562192b0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IFilteredContainerSlot.kt @@ -0,0 +1,5 @@ +package ru.dbotthepony.mc.otm.container + +interface IFilteredContainerSlot : IContainerSlot { + var filter: ItemFilter +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt deleted file mode 100644 index 03644d292..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/IMatteryContainer.kt +++ /dev/null @@ -1,255 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import it.unimi.dsi.fastutil.ints.IntIterable -import net.minecraft.world.item.Item -import net.minecraft.world.item.ItemStack -import net.minecraft.world.item.Items -import net.minecraft.world.item.crafting.RecipeInput -import ru.dbotthepony.mc.otm.core.collect.filter -import ru.dbotthepony.mc.otm.core.collect.map -import ru.dbotthepony.mc.otm.core.isNotEmpty - -interface IMatteryContainer : IContainer, RecipeInput, Iterable { - fun getSlotFilter(slot: Int): Item? - - /** - * @return whenever the filter was set. Returns false only if container can't be filtered. - */ - fun setSlotFilter(slot: Int, filter: Item? = null): Boolean { - return false - } - - fun clearSlotFilters() - - override fun isEmpty(): Boolean - fun setChanged(slot: Int) - - override fun size(): Int { - return containerSize - } - - /** - * Iterates over non-empty itemstacks of this container - */ - override fun iterator(): Iterator { - return iterator(true) - } - - /** - * Iterates non-empty slots of this container - */ - fun slotIterator(): Iterator { - return slotIterator(true) - } - - fun iterator(nonEmpty: Boolean): Iterator { - if (nonEmpty) { - return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty } - } else { - return (0 until containerSize).iterator().map { this[it] } - } - } - - open class ContainerSlot(override val slot: Int, override val container: IMatteryContainer) : IContainerSlot { - override val isForbiddenForAutomation: Boolean - get() = container.isSlotForbiddenForAutomation(slot) - - override fun getFilter(): Item? { - return container.getSlotFilter(slot) - } - - override fun setFilter(filter: Item?): Boolean { - return container.setSlotFilter(slot, filter) - } - - override fun getMaxStackSize(item: ItemStack): Int { - return container.getMaxStackSize(slot, item) - } - - override fun setChanged() { - container.setChanged(slot) - } - } - - /** - * Iterates either non-empty slots of container or all slots of container - */ - fun slotIterator(nonEmpty: Boolean): Iterator { - if (nonEmpty) { - return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { containerSlot(it) } - } else { - return (0 until containerSize).iterator().map { containerSlot(it) } - } - } - - fun containerSlot(slot: Int): IContainerSlot { - return ContainerSlot(slot, this) - } - - fun hasSlotFilter(slot: Int) = getSlotFilter(slot) !== null - fun isSlotForbiddenForAutomation(slot: Int) = getSlotFilter(slot) === Items.AIR - - fun testSlotFilter(slot: Int, itemStack: ItemStack): Boolean { - return testSlotFilter(slot, itemStack.item) - } - - fun testSlotFilter(slot: Int, item: Item): Boolean { - if (getSlotFilter(slot) == null) { - return true - } else { - return getSlotFilter(slot) === item - } - } - - fun getMaxStackSize(slot: Int, itemStack: ItemStack) = maxStackSize.coerceAtMost(itemStack.maxStackSize) - - private fun addItem(stack: ItemStack, simulate: Boolean, filterPass: Boolean, slots: IntIterable, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack { - if (stack.isEmpty) - return stack - - // двигаем в одинаковые слоты - var i = slots.intIterator() - - while (i.hasNext()) { - val slot = i.nextInt() - - if ( - (ignoreFilters || !isSlotForbiddenForAutomation(slot)) && - ItemStack.isSameItemSameComponents(getItem(slot), stack) && - (ignoreFilters || !filterPass && !hasSlotFilter(slot) || filterPass && hasSlotFilter(slot) && testSlotFilter(slot, stack)) - ) { - val slotStack = getItem(slot) - val slotLimit = getMaxStackSize(slot, slotStack) - - if (slotStack.count < slotLimit) { - val newCount = (slotStack.count + stack.count).coerceAtMost(slotLimit) - val diff = newCount - slotStack.count - - if (!simulate) { - slotStack.count = newCount - setChanged(slot) - - if (popTime != null) { - slotStack.popTime = popTime - } - } - - stack.shrink(diff) - - if (stack.isEmpty) { - return stack - } - } - } - } - - if (!onlyIntoExisting) { - i = slots.intIterator() - - // двигаем в пустые слоты - while (i.hasNext()) { - val slot = i.nextInt() - - if ( - getItem(slot).isEmpty && - (ignoreFilters || !isSlotForbiddenForAutomation(slot)) && - (ignoreFilters || !filterPass && !hasSlotFilter(slot) || filterPass && hasSlotFilter(slot) && testSlotFilter(slot, stack)) - ) { - val diff = stack.count.coerceAtMost(getMaxStackSize(slot, stack)) - - if (!simulate) { - val copyToPut = stack.copy() - copyToPut.count = diff - setItem(slot, copyToPut) - - if (popTime != null) { - copyToPut.popTime = popTime - } - } - - stack.shrink(diff) - - if (stack.isEmpty) { - return stack - } - } - } - } - - return stack - } - - fun addItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): ItemStack { - if (stack.isEmpty) - return stack - - if (ignoreFilters) { - return addItem(stack.copy(), simulate, true, slots, onlyIntoExisting, popTime, true) - } else { - var copy = addItem(stack.copy(), simulate, true, slots, onlyIntoExisting, popTime, false) - copy = addItem(copy, simulate, false, slots, onlyIntoExisting, popTime, false) - return copy - } - } - - fun handler(filter: HandlerFilter = HandlerFilter.Both): ContainerHandler { - return ContainerHandler(this, filter) - } - - /** - * Unlike [addItem], modifies original [stack] - * - * @return Whenever [stack] was modified - */ - fun consumeItem(stack: ItemStack, simulate: Boolean, slots: IntIterable = slotRange, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): Boolean { - if (stack.isEmpty) - return false - - val result = addItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters) - - if (result.count != stack.count) { - if (!simulate) { - stack.count = result.count - } - - return true - } - - return false - } - - fun fullyAddItem(stack: ItemStack, slots: IntIterable = slotRange, ignoreFilters: Boolean = false): Boolean { - if (!addItem(stack, true, slots, ignoreFilters).isEmpty) - return false - - return addItem(stack, false, slots, ignoreFilters).isEmpty - } - - fun toList(): MutableList { - val list = ArrayList(size()) - - for (i in 0 until size()) { - list.add(this[i]) - } - - return list - } - - fun shrink(slot: Int, amount: Int): Boolean { - if (slot < 0 || slot > size()) - return false - - val item = this[slot] - if (item.isEmpty) - return false - - if (item.count <= amount) { - this[slot] = ItemStack.EMPTY - } else { - item.shrink(amount) - setChanged(slot) - } - - return true - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt new file mode 100644 index 000000000..5a39fbd8a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ISlottedContainer.kt @@ -0,0 +1,36 @@ +package ru.dbotthepony.mc.otm.container + +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.kommons.collect.any + +/** + * Skeletal implementation for containers which revolve around [IContainerSlot] + */ +interface ISlottedContainer : IEnhancedContainer { + override fun setChanged(slot: Int) { + containerSlot(slot).setChanged() + } + + override fun getMaxStackSize(slot: Int, itemStack: ItemStack): Int { + return containerSlot(slot).maxStackSize(itemStack) + } + + override fun getItem(slot: Int): ItemStack { + return containerSlot(slot).item + } + + override fun removeItem(slot: Int, amount: Int): ItemStack { + return containerSlot(slot).remove(amount) + } + + override fun removeItemNoUpdate(slot: Int): ItemStack { + return containerSlot(slot).remove() + } + + override fun setItem(slot: Int, itemStack: ItemStack) { + containerSlot(slot).item = itemStack + } + + override val hasFilterableSlots: Boolean + get() = slotIterator().any { it is IFilteredContainerSlot } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt index 4f0f53114..762b65a94 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt @@ -1,124 +1,205 @@ package ru.dbotthepony.mc.otm.container +import com.google.common.collect.ImmutableSet +import com.mojang.datafixers.util.Either import com.mojang.serialization.Codec -import com.mojang.serialization.codecs.RecordCodecBuilder -import it.unimi.dsi.fastutil.objects.ObjectArrayList +import com.mojang.serialization.DataResult +import com.mojang.serialization.MapCodec +import net.minecraft.core.component.DataComponentPatch +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.core.registries.Registries +import net.minecraft.resources.ResourceLocation import net.minecraft.tags.TagKey -import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import net.neoforged.bus.api.IEventBus +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.registry.MBuiltInRegistries +import ru.dbotthepony.mc.otm.registry.MDeferredRegister +import ru.dbotthepony.mc.otm.registry.MRegistries +import java.util.function.Predicate +import kotlin.jvm.optionals.getOrElse -class ItemFilter private constructor(private val filter: Array, val isWhitelist: Boolean, val matchTag: Boolean, val matchComponents: Boolean) { - constructor(size: Int, isWhitelist: Boolean = false, matchTag: Boolean = false, matchComponents: Boolean = false) : this(Array(size) { ItemStack.EMPTY }, isWhitelist, matchTag, matchComponents) - constructor(list: List, isWhitelist: Boolean = false, matchTag: Boolean = false, matchComponents: Boolean = false) : this(list.toTypedArray(), isWhitelist, matchTag, matchComponents) - - override fun equals(other: Any?): Boolean { - return this === other || other is ItemFilter && - this.filter.contentEquals(other.filter) && - this.isWhitelist == other.isWhitelist && - this.matchTag == other.matchTag && - this.matchComponents == other.matchComponents +interface ItemFilter : Predicate { + interface Type { + val codec: MapCodec } - override fun hashCode(): Int { - return filter.contentHashCode() + val type: Type<*> + + /** + * Whenever [test] will return `true` no matter the argument, + * effectively telling that this filter is "allow all" / "no filter specified" + * + * Can be treated as (but is not equal to) "isEmpty" + */ + val allowAll: Boolean + get() = false + + /** + * Whenever [test] will return `false` no matter the argument, + * effectively telling that this filter is "deny all" / "forbidden for automation" + */ + val denyAll: Boolean + get() = false + + /** + * Whenever this filter has meaningful rules behind it, e.g. [test] will return either `true` or `false`, + * depending on value passed. + * + * In other words, returns whenever [denyAll] and [allowAll] are both `false`. + */ + val hasRules: Boolean + get() = !denyAll && !allowAll + + val depth: Int + get() = 1 + + val displayItems: ImmutableSet + get() = ImmutableSet.of() + + private data class Item(val item: net.minecraft.world.item.Item) : ItemFilter { + override val type: Type<*> + get() = Companion + + override val denyAll: Boolean + get() = item === Items.AIR + + override fun test(t: ItemStack): Boolean { + return t.isNotEmpty && t.item === item + } + + override val displayItems: ImmutableSet + get() = ImmutableSet.of(ItemStackKey(item)) + + companion object : Type { + override val codec: MapCodec by lazy { + BuiltInRegistries.ITEM.byNameCodec().xmap(::Item, Item::item).fieldOf("item") + } + } } - val size: Int - get() = filter.size + private data class Tag(val tag: TagKey) : ItemFilter { + override val type: Type<*> + get() = Companion - fun set(index: Int, value: ItemStack): ItemFilter { - if (ItemStack.isSameItemSameComponents(filter[index], value) || !value.isEmpty && filter.any { ItemStack.isSameItemSameComponents(it, value) }) - return this + override fun test(t: ItemStack): Boolean { + return t.`is`(tag) + } - return copy(filter.copyOf().also { it[index] = value }) + // TODO: can not be "lazy" cached because this will break with /reload command + override val displayItems: ImmutableSet get() { + return BuiltInRegistries.ITEM + .getTag(tag) + .map { it.stream().map { ItemStackKey(it.value()) }.collect(ImmutableSet.toImmutableSet()) } + .orElseGet { ImmutableSet.of() } + } + + companion object : Type { + override val codec: MapCodec by lazy { + TagKey.codec(Registries.ITEM).xmap(::Tag, Tag::tag).fieldOf("tag") + } + } } - operator fun get(index: Int): ItemStack { - return filter[index] - } + private object DenyAll : ItemFilter, Type { + override val type: Type<*> + get() = this - private fun copy( - filter: Array = this.filter, - isWhitelist: Boolean = this.isWhitelist, - matchTag: Boolean = this.matchTag, - matchComponents: Boolean = this.matchComponents, - ) = ItemFilter(filter, isWhitelist, matchTag, matchComponents) + override val codec: MapCodec = MapCodec.unit(this) - fun isWhitelist(flag: Boolean): ItemFilter { - if (flag == isWhitelist) - return this - else - return copy(isWhitelist = flag) - } + override val denyAll: Boolean + get() = true - fun matchTag(flag: Boolean): ItemFilter { - if (flag == matchTag) - return this - else - return copy(matchTag = flag) - } - - fun matchComponents(flag: Boolean): ItemFilter { - if (flag == matchComponents) - return this - else - return copy(matchComponents = flag) - } - - fun match(value: ItemStack): Boolean { - if (value.isEmpty) { + override fun test(t: ItemStack): Boolean { return false } - - if (filter.isEmpty()) { - return !isWhitelist - } - - for (item in filter) { - var matches = item.`is`(value.item) - - if (matches && matchTag) { - matches = false - - val thisTags = item.tags - val stackTags = HashSet>() - - for (tag in value.tags) { - stackTags.add(tag) - } - - for (tag1 in thisTags) { - if (stackTags.contains(tag1)) { - matches = true - break - } - } - } - - if (matches && matchComponents) { - matches = item.components == value.components - } - - if (matches) { - return isWhitelist - } - } - - return !isWhitelist } - companion object { - val EMPTY = ItemFilter(0) + companion object : ItemFilter, Type { + const val MAX_DEPTH = 16 + + private fun roll(input: ItemFilter): Either { + if (input is Item) { + return Either.left(input) + } else if (input is ItemStackKey && input.components == DataComponentPatch.EMPTY) { + return Either.left(Item(input.item)) + } else { + return Either.right(input) + } + } val CODEC: Codec by lazy { - RecordCodecBuilder.create { - it.group( - Codec.list(ItemStack.OPTIONAL_CODEC, 0, 40).fieldOf("filter").forGetter { ObjectArrayList.wrap(it.filter) }, - Codec.BOOL.optionalFieldOf("isWhitelist", false).forGetter { it.isWhitelist }, - Codec.BOOL.optionalFieldOf("matchTag", false).forGetter { it.matchTag }, - Codec.BOOL.optionalFieldOf("matchComponents", false).forGetter { it.matchComponents }, - ).apply(it, ::ItemFilter) - } + val codecA = BuiltInRegistries.ITEM + .byNameCodec() + .xmap(::Item, Item::item) + + val codecB = MBuiltInRegistries.ITEM_FILTER + .byNameCodec() + .dispatch(ItemFilter::type, { it.codec }) + + Codec.either(codecA, codecB) + .xmap({ it.right().getOrElse { it.left().get() } }, ::roll) + .validate { + if (it.depth <= MAX_DEPTH) { + return@validate DataResult.success(it) + } else { + return@validate DataResult.error { "Too deep item filter, max depth of $MAX_DEPTH is allowed (depth: ${it.depth})" } + } + } + } + + val EMPTY: ItemFilter + get() = this + + val DENY_ALL: ItemFilter + get() = DenyAll + + private val registrar = MDeferredRegister(MRegistries.ITEM_FILTER) + + init { + registrar.register("item") { Item.Companion } + registrar.register("item_stack") { ItemStackKey.Companion } + registrar.register("tag") { Tag.Companion } + registrar.register("set") { ItemFilterSet.Companion } + registrar.register("empty") { this } + registrar.register("deny_all") { DenyAll } + } + + internal fun register(bus: IEventBus) { + registrar.register(bus) + } + + override val type: Type<*> + get() = this + + override fun test(t: ItemStack): Boolean { + return true + } + + override val codec: MapCodec = MapCodec.unit(this) + + override val allowAll: Boolean + get() = true + + @JvmStatic + fun item(item: net.minecraft.world.item.Item): ItemFilter { + return Item(item) + } + + @JvmStatic + fun item(item: ItemStack): ItemFilter { + return Item(item.item) + } + + @JvmStatic + fun itemAndComponents(item: ItemStack): ItemFilter { + return ItemStackKey(item) + } + + @JvmStatic + fun tag(tag: TagKey): ItemFilter { + return Tag(tag) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilterSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilterSet.kt new file mode 100644 index 000000000..18c43bafe --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilterSet.kt @@ -0,0 +1,128 @@ +package ru.dbotthepony.mc.otm.container + +import com.google.common.collect.ImmutableSet +import com.mojang.serialization.Codec +import com.mojang.serialization.MapCodec +import com.mojang.serialization.codecs.RecordCodecBuilder +import it.unimi.dsi.fastutil.objects.ObjectArrayList +import net.minecraft.world.item.ItemStack + +data class ItemFilterSet(val filter: ImmutableSet, val isWhitelist: Boolean = false) : ItemFilter { + constructor(list: Collection, isWhitelist: Boolean = false) : this(ImmutableSet.copyOf(list), isWhitelist) + + val size: Int + get() = filter.size + + override val allowAll: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION) { + filter.isEmpty() && !isWhitelist || filter.isNotEmpty() && isWhitelist && filter.any { it.allowAll } + } + + override val denyAll: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION) { + filter.isEmpty() && isWhitelist || filter.isNotEmpty() && !isWhitelist && filter.any { it.denyAll } + } + + fun replace(index: Int, value: ItemFilter): ItemFilterSet { + if (index !in filter.indices || value in filter) + return this + + val values = ObjectArrayList(filter) + values[index] = value + return copy(filter = ImmutableSet.copyOf(values)) + } + + fun addOrReplace(index: Int, value: ItemFilter): ItemFilterSet { + if (index !in filter.indices) + return add(value) + else + return replace(index, value) + } + + fun add(value: ItemFilter): ItemFilterSet { + if (value in filter) + return this + + val values = ObjectArrayList(filter) + values.add(value) + return copy(filter = ImmutableSet.copyOf(values)) + } + + fun removeAt(index: Int): ItemFilterSet { + if (index !in filter.indices) + return this + + if (filter.size == 1) + return copy(filter = ImmutableSet.of()) + + val values = ObjectArrayList(filter) + values.removeAt(index) + return copy(filter = ImmutableSet.copyOf(values)) + } + + fun indexOf(value: ItemFilter): Int { + return filter.asList().indexOf(value) + } + + operator fun get(index: Int): ItemFilter { + return filter.asList().getOrElse(index) { ItemFilter.EMPTY } + } + + fun clear(): ItemFilterSet { + if (filter.isEmpty()) + return this + + return copy(filter = ImmutableSet.of()) + } + + fun isWhitelist(flag: Boolean): ItemFilterSet { + if (flag == isWhitelist) + return this + else + return copy(isWhitelist = flag) + } + + override fun test(value: ItemStack): Boolean { + return if (denyAll || value.isEmpty) + false + else if (allowAll) + true + else if (filter.any { it.test(value) }) + isWhitelist + else + !isWhitelist + } + + override val type: ItemFilter.Type<*> + get() = Companion + + override val depth: Int by lazy { + if (filter.isNotEmpty()) + return@lazy 1 + filter.maxOf { it.depth } + + return@lazy 1 + } + + // TODO: can not be "lazy" cached because that will break with /reload command + override val displayItems: ImmutableSet get() { + val sub = filter.map { it.displayItems } + val results = ArrayList(sub.sumOf { it.size }) + sub.forEach { results.addAll(it) } + return ImmutableSet.copyOf(results) + } + + companion object : ItemFilter.Type { + override val codec: MapCodec by lazy { + RecordCodecBuilder.mapCodec { + it.group( + Codec.list(ItemFilter.CODEC, 0, 40).fieldOf("filter").forGetter { ObjectArrayList(it.filter) }, + Codec.BOOL.optionalFieldOf("isWhitelist", false).forGetter { it.isWhitelist }, + ).apply(it, ::ItemFilterSet) + } + } + + val EMPTY = ItemFilterSet(ImmutableSet.of()) + + val CODEC: Codec by lazy { + codec.codec() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemStackKey.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemStackKey.kt new file mode 100644 index 000000000..9fc344f59 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemStackKey.kt @@ -0,0 +1,83 @@ +package ru.dbotthepony.mc.otm.container + +import com.google.common.collect.ImmutableSet +import com.mojang.serialization.Codec +import com.mojang.serialization.MapCodec +import com.mojang.serialization.codecs.RecordCodecBuilder +import it.unimi.dsi.fastutil.HashCommon +import net.minecraft.core.component.DataComponentPatch +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.core.getHolder + +class ItemStackKey(val item: Item, val components: DataComponentPatch) : ItemFilter { + // make copy of original itemstack because there is no copy() method on DataComponentMap, which is returned by ItemStack#getComponents + constructor(itemStack: ItemStack) : this(itemStack.item, itemStack.copy().componentsPatch) + constructor(item: Item) : this(item, DataComponentPatch.EMPTY) + + private var hashComputed = false + private var hash = 0 + + override fun equals(other: Any?): Boolean { + return this === other || other is ItemStackKey && other.item === item && other.components == components + } + + override fun hashCode(): Int { + if (!hashComputed) { + hash = HashCommon.murmurHash3(item.hashCode().toLong().shl(32) or components.hashCode().toLong()).toInt() + hashComputed = true + } + + return hash + } + + fun asItemStack(count: Int = 1): ItemStack { + return ItemStack(BuiltInRegistries.ITEM.getHolder(item)!!, count, components) + } + + override fun toString(): String { + return "ItemStackKey[$item, $components]" + } + + override fun test(t: ItemStack): Boolean { + return t.item === item && t.componentsPatch == components + } + + override val type: ItemFilter.Type<*> + get() = Companion + + override val displayItems: ImmutableSet = ImmutableSet.of(this) + + companion object : ItemFilter.Type { + override val codec: MapCodec + get() = MAP_CODEC + + val MAP_CODEC: MapCodec by lazy { + RecordCodecBuilder.mapCodec { + it.group( + BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter(ItemStackKey::item), + DataComponentPatch.CODEC.fieldOf("components").forGetter(ItemStackKey::components) + ).apply(it, ::ItemStackKey) + } + } + + val CODEC: Codec by lazy { + MAP_CODEC.codec() + } + } +} + +fun ItemStack.asKey(): ItemStackKey { + return ItemStackKey(this) +} + +fun ItemStack.asKeyOrNull(): ItemStackKey? { + if (isEmpty) return null + return ItemStackKey(this) +} + +fun Item.asKey(): ItemStackKey { + return ItemStackKey(this) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt deleted file mode 100644 index 7cee2b9e7..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt +++ /dev/null @@ -1,501 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import com.mojang.serialization.Codec -import com.mojang.serialization.codecs.RecordCodecBuilder -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.ints.IntSpliterator -import it.unimi.dsi.fastutil.objects.ObjectSpliterators -import net.minecraft.core.HolderLookup -import net.minecraft.core.registries.BuiltInRegistries -import net.minecraft.world.item.ItemStack -import net.minecraft.nbt.CompoundTag -import net.minecraft.nbt.NbtOps -import net.minecraft.nbt.Tag -import net.minecraft.world.Container -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.neoforged.neoforge.common.util.INBTSerializable -import org.apache.logging.log4j.LogManager -import ru.dbotthepony.kommons.util.Delegate -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.emptyIterator -import ru.dbotthepony.mc.otm.core.collect.filter -import ru.dbotthepony.mc.otm.core.collect.map -import ru.dbotthepony.mc.otm.core.collect.toList -import ru.dbotthepony.mc.otm.core.immutableList -import ru.dbotthepony.mc.otm.core.isNotEmpty -import ru.dbotthepony.mc.otm.data.codec.minRange -import ru.dbotthepony.mc.otm.network.StreamCodecs -import ru.dbotthepony.mc.otm.network.syncher.ISynchable -import ru.dbotthepony.mc.otm.network.syncher.SynchableObservedDelegate -import java.util.* -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.collections.ArrayList - -@Suppress("UNUSED") -open class MatteryContainer(var listener: ContainerListener, private val size: Int) : IMatteryContainer, INBTSerializable, StackedContentsCompatible { - constructor(watcher: Runnable, size: Int) : this({ _, _, _ -> watcher.run() }, size) - constructor(size: Int) : this(EmptyListener, size) - - fun interface ContainerListener { - fun setChanged(slot: Int, new: ItemStack, old: ItemStack) - } - - object EmptyListener : ContainerListener { - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {} - } - - init { - require(size >= 0) { "Invalid container size $size" } - } - - private val slots = Array(size) { ItemStack.EMPTY } - private val nonEmptyFlags = BitSet() - private var nonEmptyIndices = IntArrayList() - private var indicesReferenced = false - private data class Update(val slot: Int, val new: ItemStack, val old: ItemStack) - private val queuedUpdates = ArrayList() - private var queueUpdates = false - - private fun cowIndices() { - if (indicesReferenced) { - nonEmptyIndices = IntArrayList(nonEmptyIndices) - indicesReferenced = false - } - } - - private val trackedSlots: Array = Array(size) { ItemStack.EMPTY } - private val filters: Array = arrayOfNulls(size) - - var changeset = 0 - private set - - override fun clearSlotFilters() { - Arrays.fill(filters, null) - } - - val synchableFilters by lazy { - immutableList { - for (i in 0 until size) { - accept(SynchableObservedDelegate(Delegate.Of({ filters[i] }, { filters[i] = it }), StreamCodecs.ITEM_TYPE_NULLABLE)) - } - } - } - - final override fun setSlotFilter(slot: Int, filter: Item?): Boolean { - filters[slot] = filter - return true - } - - final override fun getSlotFilter(slot: Int) = filters[slot] - final override fun hasSlotFilter(slot: Int) = filters[slot] !== null - final override fun isSlotForbiddenForAutomation(slot: Int) = filters[slot] === Items.AIR - - final override fun testSlotFilter(slot: Int, itemStack: ItemStack): Boolean { - return testSlotFilter(slot, itemStack.item) - } - - final override fun testSlotFilter(slot: Int, item: Item): Boolean { - if (filters[slot] == null) { - return true - } else { - return filters[slot] === item - } - } - - final override fun getContainerSize() = size - protected open fun startedIgnoringUpdates() {} - protected open fun stoppedIgnoringUpdates() {} - - private data class SerializedItem(val item: ItemStack, val slot: Int) { - companion object { - val CODEC: Codec = RecordCodecBuilder.create { - it.group( - ItemStack.OPTIONAL_CODEC.fieldOf("item").forGetter { it.item }, - Codec.INT.minRange(0).fieldOf("slot").forGetter { it.slot }, - ).apply(it, ::SerializedItem) - } - } - } - - private data class SerializedFilter(val item: Item, val slot: Int) { - companion object { - val CODEC: Codec = RecordCodecBuilder.create { - it.group( - BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter { it.item }, - Codec.INT.minRange(0).fieldOf("slot").forGetter { it.slot }, - ).apply(it, ::SerializedFilter) - } - } - } - - private data class SerializedState( - val items: List, - val filters: List - ) { - companion object { - val CODEC: Codec = RecordCodecBuilder.create { - it.group( - Codec.list(SerializedItem.CODEC).fieldOf("items").forGetter { it.items }, - Codec.list(SerializedFilter.CODEC).fieldOf("filters").forGetter { it.filters }, - ).apply(it, ::SerializedState) - } - } - } - - override fun deserializeNBT(registries: HolderLookup.Provider, tag: Tag?) { - Arrays.fill(slots, ItemStack.EMPTY) - Arrays.fill(filters, null) - nonEmptyFlags.clear() - nonEmptyIndices = IntArrayList() - - if (tag != null) { - SerializedState.CODEC.parse(registries.createSerializationContext(NbtOps.INSTANCE), tag) - .resultOrPartial { LOGGER.error("Error deserializing container: $it") } - .ifPresent { - val freeSlots = IntAVLTreeSet() - - for (i in 0 until size) - freeSlots.add(i) - - for ((item, slotID) in it.items) { - if (item.isEmpty) - continue - - if (freeSlots.remove(slotID)) { - slots[slotID] = item - // trackedSlots[slotID] = item.copy() - } else if (freeSlots.isEmpty()) { - break - } else { - val slotID = freeSlots.firstInt() - freeSlots.remove(slotID) - slots[slotID] = item - // trackedSlots[slotID] = item.copy() - } - } - - for ((item, index) in it.filters) { - if (index in 0 until size) - filters[index] = item - } - - setChanged() - } - } - } - - private fun internalSetChanged(slot: Int, new: ItemStack, old: ItemStack) { - if (queueUpdates) { - queuedUpdates.add(Update(slot, new, old)) - } else { - setChanged(slot, new, old) - } - } - - private fun runUpdates() { - for ((slot, new, old) in queuedUpdates) { - setChanged(slot, new, old) - } - - queuedUpdates.clear() - } - - protected open fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - listener.setChanged(slot, new, old) - } - - override fun serializeNBT(registries: HolderLookup.Provider): CompoundTag { - val state = SerializedState( - slotIterator(true).map { SerializedItem(it.item, it.slot) }.toList(size), - filters.withIndex().iterator().filter { it.value != null }.map { SerializedFilter(it.value!!, it.index) }.toList() - ) - - return SerializedState.CODEC.encodeStart(registries.createSerializationContext(NbtOps.INSTANCE), state) - .resultOrPartial { throw RuntimeException("Failed to encode container contents: $it") }.get() as CompoundTag - } - - final override fun isEmpty(): Boolean { - return nonEmptyIndices.isEmpty - } - - @Suppress("nothing_to_inline") - inline operator fun get(slot: Int) = getItem(slot) - - @Suppress("nothing_to_inline") - inline operator fun set(slot: Int, stack: ItemStack) = setItem(slot, stack) - - operator fun contains(other: ItemStack): Boolean { - for (i in 0 until size) { - if (ItemStack.isSameItemSameComponents(this[i], other)) { - return true - } - } - - return false - } - - final override fun getItem(slot: Int): ItemStack { - val item = slots[slot] - - if (item.isEmpty) { - if (nonEmptyFlags[slot]) { - setChanged(slot) - } - - return ItemStack.EMPTY - } else { - if (!nonEmptyFlags[slot]) { - setChanged(slot) - } - - return item - } - } - - 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 - - val old = slots[slot].copy() - val split = slots[slot].split(amount) - trackedSlots[slot] = slots[slot].copy() - changeset++ - updateEmptyFlag(slot) - internalSetChanged(slot, if (slots[slot].isEmpty) ItemStack.EMPTY else slots[slot], old) - - return split - } - - final override fun removeItemNoUpdate(slot: Int): ItemStack { - val old = slots[slot] - slots[slot] = ItemStack.EMPTY - trackedSlots[slot] = ItemStack.EMPTY - - if (old.isNotEmpty) { - updateEmptyFlag(slot) - changeset++ - } - - return old - } - - final override fun setItem(slot: Int, itemStack: ItemStack) { - if (slots[slot].isEmpty && itemStack.isEmpty || itemStack === slots[slot]) - return - - val old = slots[slot] - slots[slot] = if (itemStack.isEmpty) ItemStack.EMPTY else itemStack - trackedSlots[slot] = if (itemStack.isEmpty) ItemStack.EMPTY else itemStack.copy() - - updateEmptyFlag(slot) - changeset++ - internalSetChanged(slot, itemStack, old) - } - - final override fun setChanged() { - queueUpdates = true - - try { - for (slot in 0 until size) { - setChanged(slot) - } - - runUpdates() - } finally { - queuedUpdates.clear() - queueUpdates = false - } - } - - final override fun setChanged(slot: Int) { - if (!ItemStack.isSameItemSameComponents(slots[slot], trackedSlots[slot])) { - trackedSlots[slot] = slots[slot].copy() - updateEmptyFlag(slot) - changeset++ - internalSetChanged(slot, slots[slot], trackedSlots[slot]) - // mojang соси))0)0))0)))))0) - } - } - - private fun updateEmptyFlag(slot: Int) { - if (slots[slot].isEmpty) { - if (nonEmptyFlags[slot]) { - nonEmptyFlags[slot] = false - cowIndices() - nonEmptyIndices.rem(slot) - } - } else { - if (!nonEmptyFlags[slot]) { - nonEmptyFlags[slot] = true - cowIndices() - nonEmptyIndices.addSorted(slot, IntComparators.NATURAL_COMPARATOR) - } - } - } - - override fun stillValid(player: Player): Boolean { - return true - } - - final override fun clearContent() { - nonEmptyFlags.clear() - nonEmptyIndices = IntArrayList() - - Arrays.fill(trackedSlots, ItemStack.EMPTY) - - for (slot in 0 until size) { - if (!slots[slot].isEmpty) { - val old = slots[slot] - slots[slot] = ItemStack.EMPTY - internalSetChanged(slot, ItemStack.EMPTY, old) - } - } - } - - private inner class Iterator : kotlin.collections.Iterator { - init { - indicesReferenced = true - } - - private val parent = nonEmptyIndices.intIterator() - private var lastIndex = -1 - - override fun hasNext(): Boolean { - return parent.hasNext() - } - - override fun next(): ItemStack { - lastIndex = parent.nextInt() - return getItem(lastIndex) - } - } - - private inner class Spliterator(private val parent: IntSpliterator) : java.util.Spliterator { - override fun tryAdvance(action: Consumer): Boolean { - return parent.tryAdvance { - action.accept(getItem(it)) - } - } - - override fun trySplit(): java.util.Spliterator? { - return parent.trySplit()?.let(::Spliterator) - } - - override fun estimateSize(): Long { - return parent.estimateSize() - } - - override fun characteristics(): Int { - return parent.characteristics() - } - } - - final override fun iterator(): kotlin.collections.Iterator { - if (isEmpty) { - return emptyIterator() - } - - return Iterator() - } - - final override fun iterator(nonEmpty: Boolean): kotlin.collections.Iterator { - if (!nonEmpty) { - return (0 until size).iterator().map { slots[it] } - } else if (isEmpty) { - return emptyIterator() - } else { - return Iterator() - } - } - - private inner class Slot(override val slot: Int) : IContainerSlot { - override val container: Container - get() = this@MatteryContainer - - override val isForbiddenForAutomation: Boolean - get() = isSlotForbiddenForAutomation(slot) - - override fun getFilter(): Item? { - return getSlotFilter(slot) - } - - override fun setFilter(filter: Item?): Boolean { - return setSlotFilter(slot, filter) - } - - override fun getMaxStackSize(item: ItemStack): Int { - return getMaxStackSize(slot, item) - } - - override fun setChanged() { - setChanged(slot) - } - } - - final override fun slotIterator(): kotlin.collections.Iterator { - indicesReferenced = true - return nonEmptyIndices.iterator().map { Slot(it) } - } - - final override fun slotIterator(nonEmpty: Boolean): kotlin.collections.Iterator { - if (!nonEmpty) { - return (0 until size).iterator().map { Slot(it) } - } else if (isEmpty) { - return emptyIterator() - } else { - indicesReferenced = true - return nonEmptyIndices.iterator().map { Slot(it) } - } - } - - final override fun containerSlot(slot: Int): IContainerSlot { - return Slot(slot) - } - - final override fun countItem(item: Item): Int { - return iterator().filter { it.item == item }.count().toInt() - } - - final override fun hasAnyOf(items: Set): Boolean { - return iterator().any { it.item in items } - } - - final override fun hasAnyMatching(predicate: Predicate): Boolean { - return iterator().any(predicate) - } - - final override fun spliterator(): java.util.Spliterator { - if (isEmpty) { - return ObjectSpliterators.emptySpliterator() - } - - indicesReferenced = true - return Spliterator(nonEmptyIndices.intSpliterator()) - } - - fun stream(): Stream { - return StreamSupport.stream(spliterator(), false) - } - - companion object { - private val LOGGER = LogManager.getLogger() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt deleted file mode 100644 index 036e7bc27..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryCraftingContainer.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import net.minecraft.world.inventory.CraftingContainer -import net.minecraft.world.item.ItemStack - -open class MatteryCraftingContainer(listener: ContainerListener, private val width: Int, private val height: Int) : MatteryContainer(listener, width * height), CraftingContainer { - constructor(listener: () -> Unit, width: Int, height: Int) : this({ _, _, _ -> listener.invoke() }, width, height) - constructor(width: Int, height: Int) : this(EmptyListener, width, height) - - final override fun getWidth(): Int { - return width - } - - final override fun getHeight(): Int { - return height - } - - final override fun getItems(): MutableList { - return toList() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowContainer.kt deleted file mode 100644 index 93c85355e..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowContainer.kt +++ /dev/null @@ -1,47 +0,0 @@ -package ru.dbotthepony.mc.otm.container - -import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap -import net.minecraft.world.Container -import net.minecraft.world.item.ItemStack - -class ShadowContainer(private val parent: Container) : IContainer by IContainer.wrap(parent) { - private val shadowed = Int2ObjectArrayMap(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 } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowCraftingContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowCraftingContainer.kt deleted file mode 100644 index 95123a710..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ShadowCraftingContainer.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ru.dbotthepony.mc.otm.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.container.util.iterator -import ru.dbotthepony.mc.otm.core.collect.toList - -class ShadowCraftingContainer(private val parent: CraftingContainer) : IContainer 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 { - return iterator().toList() - } - - companion object { - fun shadow(container: CraftingContainer, slot: Int, itemStack: ItemStack): CraftingContainer { - return ShadowCraftingContainer(container).also { it[slot] = itemStack } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt index 06879d676..e3dae88f8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.container +import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.UpgradeType @@ -15,33 +16,72 @@ class UpgradeContainer( slotCount: Int, val allowedUpgrades: Set = UpgradeType.ALL, val shouldLockUpgradeSlots: BooleanSupplier = BooleanSupplier { false }, - listener: Runnable = Runnable {} -) : MatteryContainer(listener, slotCount), IMatteryUpgrade { + private val listener: Runnable = Runnable {} +) : EnhancedContainer.Simple(slotCount), IMatteryUpgrade { + override fun notifySlotChanged(slot: Int, old: ItemStack) { + super.notifySlotChanged(slot, old) + listener.run() + } + override val upgradeTypes: Set get() = setOf() - private fun positiveDecimals(fn: (IMatteryUpgrade) -> Decimal, reducer: (Decimal, Decimal) -> Decimal): Decimal { + private inline fun positiveDecimals(fn: (IMatteryUpgrade) -> Decimal, reducer: (Decimal, Decimal) -> Decimal): Decimal { if (isEmpty) return Decimal.ZERO - return iterator() - .map { (it.getCapability(MatteryCapability.UPGRADE)?.let(fn) ?: Decimal.ZERO).moreThanZero() * it.count } - .reduce(Decimal.ZERO, reducer) + var result = Decimal.ZERO + + for (it in iterator()) { + val cap = it.getCapability(MatteryCapability.UPGRADE) ?: continue + val value = fn(cap) + + if (value > Decimal.ZERO) { + if (it.count == 1) { + result = reducer(result, value ) + } else { + result = reducer(result, value * it.count) + } + } + } + + return result } - private fun anyDecimals(fn: (IMatteryUpgrade) -> Decimal, reducer: (Decimal, Decimal) -> Decimal): Decimal { + private inline fun anyDecimals(fn: (IMatteryUpgrade) -> Decimal, reducer: (Decimal, Decimal) -> Decimal): Decimal { if (isEmpty) return Decimal.ZERO - return iterator() - .map { (it.getCapability(MatteryCapability.UPGRADE)?.let(fn) ?: Decimal.ZERO) * it.count } - .reduce(Decimal.ZERO, reducer) + var result = Decimal.ZERO + + for (it in iterator()) { + val cap = it.getCapability(MatteryCapability.UPGRADE) ?: continue + val value = fn(cap) + + if (it.count == 1) { + result = reducer(result, value) + } else { + result = reducer(result, value * it.count) + } + } + + return result + } + + override val speedBonus: Double get() { + if (isEmpty) + return 0.0 + + return sumOf { (it.getCapability(MatteryCapability.UPGRADE)?.speedBonus ?: 0.0) * it.count } + } + + override val processingItems: Int get() { + if (isEmpty) + return 0 + + return sumOf { (it.getCapability(MatteryCapability.UPGRADE)?.processingItems ?: 0).coerceAtLeast(0) * it.count } } - override val speedBonus: Double - get() = if (isEmpty) 0.0 else iterator().map { (it.getCapability(MatteryCapability.UPGRADE)?.speedBonus ?: 0.0) * it.count }.reduce(0.0) { a, b -> a + b } - override val processingItems: Int - get() = if (isEmpty) 0 else iterator().map { (it.getCapability(MatteryCapability.UPGRADE)?.processingItems ?: 0).coerceAtLeast(0) * it.count }.reduce(0) { a, b -> a + b } override val energyStorageFlat: Decimal get() = positiveDecimals(IMatteryUpgrade::energyStorageFlat, Decimal::plus) override val energyStorage: Decimal @@ -52,8 +92,28 @@ class UpgradeContainer( get() = positiveDecimals(IMatteryUpgrade::matterStorage, Decimal::plus) override val energyConsumed: Decimal get() = anyDecimals(IMatteryUpgrade::energyConsumed, Decimal::plus) - override val failureMultiplier: Double - get() = if (isEmpty) 1.0 else iterator().map { (it.getCapability(MatteryCapability.UPGRADE)?.failureMultiplier ?: 1.0).coerceAtLeast(0.0).pow(it.count.toDouble()) }.reduce(1.0) { a, b -> a * b } + + override val failureMultiplier: Double get() { + if (isEmpty) + return 1.0 + + var result = 1.0 + + for (it in iterator()) { + val cap = it.getCapability(MatteryCapability.UPGRADE) ?: continue + val chance = cap.failureMultiplier.coerceAtLeast(0.0) + + if (chance == 0.0) + return 0.0 + else if (it.count == 1) + result *= chance + else + result *= chance.pow(it.count) + } + + return result + } + override val energyThroughputFlat: Decimal get() = positiveDecimals(IMatteryUpgrade::energyThroughputFlat, Decimal::plus) override val energyThroughput: Decimal diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/AutomationFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/AutomationFilter.kt new file mode 100644 index 000000000..43f09142f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/AutomationFilter.kt @@ -0,0 +1,51 @@ +package ru.dbotthepony.mc.otm.container.slotted + +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.container.IContainerSlot + +fun interface AutomationPlaceItem { + fun canAutomationPlaceItem(self: S, itemStack: ItemStack): Boolean +} + +fun interface AutomationTakeItem { + fun canAutomationTakeItem(self: S, desired: Int): Boolean +} + +fun interface AutomationModifyPlaceCount { + fun modifyAutomationPlaceCount(self: S, itemStack: ItemStack): Int +} + +fun interface AutomationModifyExtractionCount { + fun modifyAutomationExtractionCount(self: S, desired: Int): Int +} + +interface AutomationFilter : AutomationPlaceItem, AutomationTakeItem, AutomationModifyPlaceCount, AutomationModifyExtractionCount { + override fun modifyAutomationPlaceCount(self: S, itemStack: ItemStack): Int { + return itemStack.count + } + + override fun modifyAutomationExtractionCount(self: S, desired: Int): Int { + return desired + } +} + +fun AutomationFilter.and(other: AutomationFilter): AutomationFilter { + return object : AutomationFilter { + override fun canAutomationPlaceItem(self: T, itemStack: ItemStack): Boolean { + return this@and.canAutomationPlaceItem(self, itemStack) && other.canAutomationPlaceItem(self, itemStack) + } + + override fun canAutomationTakeItem(self: T, desired: Int): Boolean { + return this@and.canAutomationTakeItem(self, desired) && other.canAutomationTakeItem(self, desired) + } + + override fun modifyAutomationPlaceCount(self: T, itemStack: ItemStack): Int { + return this@and.modifyAutomationPlaceCount(self, itemStack) + } + + override fun modifyAutomationExtractionCount(self: T, desired: Int): Int { + return this@and.modifyAutomationExtractionCount(self, desired) + } + } +} + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/AutomationFilters.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/AutomationFilters.kt new file mode 100644 index 000000000..b6d5e95a4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/AutomationFilters.kt @@ -0,0 +1,163 @@ +package ru.dbotthepony.mc.otm.container.slotted + +import net.minecraft.world.item.ItemStack +import net.neoforged.neoforge.capabilities.Capabilities +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.fluid.stream +import ru.dbotthepony.mc.otm.container.IContainerSlot +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.math.Decimal + +enum class AutomationFilters : AutomationFilter { + ONLY_OUT { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return false + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return true + } + }, + + ONLY_IN { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return true + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return false + } + }, + + ALLOW { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return true + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return true + } + }, + + DENY { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return false + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return false + } + }, + + FLUID_CONTAINERS { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.tanks > 0 } ?: false + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return true + } + }, + + DRAINABLE_FLUID_CONTAINERS { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.let { it.stream().anyMatch { it.isNotEmpty } } ?: false + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return !canAutomationPlaceItem(self, self.item) + } + }, + + DISCHARGABLE { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return itemStack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { it.canExtract() && it.extractEnergy(Int.MAX_VALUE, true) > 0 } ?: false + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return self.item.getCapability(Capabilities.EnergyStorage.ITEM)?.let { !it.canExtract() || it.extractEnergy(Int.MAX_VALUE, true) <= 0 } ?: true + } + }, + + CHARGEABLE { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return itemStack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { it.canReceive() && it.receiveEnergy(Int.MAX_VALUE, true) > 0 } ?: false + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return self.item.getCapability(Capabilities.EnergyStorage.ITEM)?.let { !it.canReceive() || it.receiveEnergy(Int.MAX_VALUE, true) <= 0 } ?: true + } + }, + + CHEMICAL_FUEL { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return itemStack.getBurnTime(null) > 0 + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return self.item.getBurnTime(null) <= 0 + } + }, + + IS_PATTERN { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return itemStack.getCapability(MatteryCapability.PATTERN_ITEM) != null + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return true + } + }, + + MATTER_PROVIDERS { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return itemStack.getCapability(MatteryCapability.MATTER_ITEM) + ?.let { it.matterFlow.output && it.extractMatterChecked(Decimal.POSITIVE_INFINITY, true) > Decimal.ZERO } + ?: false + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return self.item.getCapability(MatteryCapability.MATTER_ITEM) + ?.let { !it.matterFlow.output || it.extractMatterChecked(Decimal.POSITIVE_INFINITY, true) <= Decimal.ZERO } + ?: true + } + }, + + MATTER_CONSUMERS { + override fun canAutomationPlaceItem(self: IContainerSlot, itemStack: ItemStack): Boolean { + return itemStack.getCapability(MatteryCapability.MATTER_ITEM) + ?.let { it.matterFlow.input && it.receiveMatterChecked(Decimal.POSITIVE_INFINITY, true) > Decimal.ZERO } + ?: false + } + + override fun canAutomationTakeItem(self: IContainerSlot, desired: Int): Boolean { + return self.item.getCapability(MatteryCapability.MATTER_ITEM) + ?.let { !it.matterFlow.input || it.receiveMatterChecked(Decimal.POSITIVE_INFINITY, true) <= Decimal.ZERO } + ?: true + } + }; + + val simpleProvider: SlottedContainer.SlotProvider by lazy(LazyThreadSafetyMode.PUBLICATION) { + ContainerSlot.Simple(filter = this) + } + + val filteredProvider: SlottedContainer.SlotProvider by lazy(LazyThreadSafetyMode.PUBLICATION) { + FilteredContainerSlot.Simple(filter = this) + } + + val unlimitedSimpleProvider: SlottedContainer.SlotProvider by lazy(LazyThreadSafetyMode.PUBLICATION) { + ContainerSlot.Simple(filter = this, maxStackSize = Int.MAX_VALUE) + } + + val unlimitedFilteredProvider: SlottedContainer.SlotProvider by lazy(LazyThreadSafetyMode.PUBLICATION) { + FilteredContainerSlot.Simple(filter = this, maxStackSize = Int.MAX_VALUE) + } + + val limitedSimpleProvider: SlottedContainer.SlotProvider by lazy(LazyThreadSafetyMode.PUBLICATION) { + ContainerSlot.Simple(filter = this, maxStackSize = 1) + } + + val limitedFilteredProvider: SlottedContainer.SlotProvider by lazy(LazyThreadSafetyMode.PUBLICATION) { + FilteredContainerSlot.Simple(filter = this, maxStackSize = 1) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/ContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/ContainerSlot.kt new file mode 100644 index 000000000..178ee81f5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/ContainerSlot.kt @@ -0,0 +1,174 @@ +package ru.dbotthepony.mc.otm.container.slotted + +import net.minecraft.core.HolderLookup +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.neoforged.neoforge.common.util.INBTSerializable +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.container.IAutomatedContainerSlot +import ru.dbotthepony.mc.otm.container.IFilteredAutomatedContainerSlot +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.data.getOrNull + +open class ContainerSlot( + protected val container: SlottedContainer, + protected val slot: Int +) : IAutomatedContainerSlot, INBTSerializable { + private var _item: ItemStack = ItemStack.EMPTY + + final override var item: ItemStack + get() = _item + set(value) { + _item = value + setChanged() + } + + private var observedItem = ItemStack.EMPTY + + // called from inside setChanged + protected open fun notifyChanged(old: ItemStack) {} + + final override fun setChanged() { + observeChanges() + } + + /** + * Called when slot needs to be cleared of any data present + */ + open fun clear() { + _item = ItemStack.EMPTY + + if (observedItem.isNotEmpty) { + notifyChanged(observedItem) + } + + observedItem = ItemStack.EMPTY + } + + fun observeChanges(): Boolean { + if (observedItem.isNotEmpty && item.isEmpty) { + notifyChanged(observedItem) + observedItem = ItemStack.EMPTY + _item = ItemStack.EMPTY + container.notifyChanged(slot) + return true + } else if (observedItem.count != item.count || !ItemStack.isSameItemSameComponents(item, observedItem)) { + notifyChanged(observedItem) + observedItem = item.copy() + container.notifyChanged(slot) + return true + } + + return false + } + + override fun remove(): ItemStack { + val item = item + + if (item.isEmpty) { + return ItemStack.EMPTY + } else { + this.item = ItemStack.EMPTY + return item + } + } + + override val maxStackSize: Int + get() = Item.DEFAULT_MAX_STACK_SIZE + + override fun remove(count: Int): ItemStack { + val item = item + + if (item.isEmpty) { + return ItemStack.EMPTY + } + + if (item.count <= count) { + this.item = ItemStack.EMPTY + return item + } else { + val split = item.split(count) + setChanged() + return split + } + } + + override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag { + return CompoundTag().also { + it["item"] = ItemStack.OPTIONAL_CODEC.encodeStart(provider.createSerializationContext(NbtOps.INSTANCE), item) + .getOrThrow { RuntimeException("Unable to serialize $item in slot $slot: $it") } + } + } + + override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) { + _item = ItemStack.OPTIONAL_CODEC.decode(provider.createSerializationContext(NbtOps.INSTANCE), nbt["item"]) + .ifError { LOGGER.error("Unable to deserialize item at slot $slot: ${it.message()}") } + .getOrNull()?.first ?: ItemStack.EMPTY + + observedItem = item.copy() + notifyChanged(ItemStack.EMPTY) + } + + class Simple( + private val listener: (new: ItemStack, old: ItemStack) -> Unit = { _, _ -> }, + private val maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE, + private val canAutomationPlaceItem: AutomationPlaceItem = AutomationPlaceItem { _, _ -> true }, + private val canAutomationTakeItem: AutomationTakeItem = AutomationTakeItem { _, _ -> true }, + private val modifyAutomationPlaceCount: AutomationModifyPlaceCount = AutomationModifyPlaceCount { _, item -> item.count }, + private val modifyAutomationExtractionCount: AutomationModifyExtractionCount = AutomationModifyExtractionCount { _, desired -> desired }, + ) : SlottedContainer.SlotProvider { + constructor( + listener: (new: ItemStack, old: ItemStack) -> Unit = { _, _ -> }, + maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE, + filter: AutomationFilter = AutomationFilters.ALLOW + ) : this(listener, maxStackSize, filter, filter, filter, filter) + + private open inner class Instance(container: SlottedContainer, slot: Int) : ContainerSlot(container, slot) { + override val maxStackSize: Int + get() = this@Simple.maxStackSize + + override fun maxStackSize(item: ItemStack): Int { + if (maxStackSize == Int.MAX_VALUE) + return Int.MAX_VALUE + + return super.maxStackSize(item) + } + + override fun notifyChanged(old: ItemStack) { + super.notifyChanged(old) + listener(item, old) + } + + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && canAutomationPlaceItem.canAutomationPlaceItem(this, itemStack) + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return super.canAutomationTakeItem(desired) && canAutomationTakeItem.canAutomationTakeItem(this, desired) + } + + override fun modifyAutomationPlaceCount(itemStack: ItemStack): Int { + return modifyAutomationPlaceCount.modifyAutomationPlaceCount(this, itemStack) + } + + override fun modifyAutomationExtractionCount(desired: Int): Int { + return modifyAutomationExtractionCount.modifyAutomationExtractionCount(this, desired) + } + } + + override fun create(container: SlottedContainer, index: Int): ContainerSlot { + return Instance(container, index) + } + } + + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt new file mode 100644 index 000000000..11a59da02 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/FilteredContainerSlot.kt @@ -0,0 +1,108 @@ +package ru.dbotthepony.mc.otm.container.slotted + +import net.minecraft.core.HolderLookup +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps +import net.minecraft.resources.ResourceLocation +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.container.IFilteredAutomatedContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.registryName +import java.util.Collections + +open class FilteredContainerSlot( + container: SlottedContainer, + slot: Int +) : ContainerSlot(container, slot), IFilteredAutomatedContainerSlot { + override var filter: ItemFilter = ItemFilter.EMPTY + set(value) { + if (field !== value) { + field = value + container.notifyChanged() + } + } + + override fun clear() { + super.clear() + filter = ItemFilter.EMPTY + } + + override fun serializeNBT(provider: HolderLookup.Provider): CompoundTag { + return super.serializeNBT(provider).also { + it["filter"] = ItemFilter.CODEC.encodeStart(provider.createSerializationContext(NbtOps.INSTANCE), filter) + .getOrThrow { RuntimeException("Failed to serialize item filter: $it") } + } + } + + override fun deserializeNBT(provider: HolderLookup.Provider, nbt: CompoundTag) { + super.deserializeNBT(provider, nbt) + + filter = ItemFilter.EMPTY + + if ("filter" in nbt) { + ItemFilter.CODEC.decode(provider.createSerializationContext(NbtOps.INSTANCE), nbt["filter"]) + .ifError { LOGGER.error("Unable to deserialize item filter: ${it.message()}") } + .resultOrPartial().map { it.first }.ifPresent { filter = it } + } + } + + class Simple( + private val listener: (new: ItemStack, old: ItemStack) -> Unit = { _, _ -> }, + private val maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE, + private val canAutomationPlaceItem: AutomationPlaceItem = AutomationPlaceItem { _, _ -> true }, + private val canAutomationTakeItem: AutomationTakeItem = AutomationTakeItem { _, _ -> true }, + private val modifyAutomationPlaceCount: AutomationModifyPlaceCount = AutomationModifyPlaceCount { _, item -> item.count }, + private val modifyAutomationExtractionCount: AutomationModifyExtractionCount = AutomationModifyExtractionCount { _, desired -> desired }, + ) : SlottedContainer.SlotProvider { + constructor( + listener: (new: ItemStack, old: ItemStack) -> Unit = { _, _ -> }, + maxStackSize: Int = Item.DEFAULT_MAX_STACK_SIZE, + filter: AutomationFilter = AutomationFilters.ALLOW + ) : this(listener, maxStackSize, filter, filter, filter, filter) + + private open inner class Instance(container: SlottedContainer, slot: Int) : FilteredContainerSlot(container, slot) { + override val maxStackSize: Int + get() = this@Simple.maxStackSize + + override fun maxStackSize(item: ItemStack): Int { + if (maxStackSize == Int.MAX_VALUE) + return Int.MAX_VALUE + + return super.maxStackSize(item) + } + + override fun notifyChanged(old: ItemStack) { + super.notifyChanged(old) + listener(item, old) + } + + override fun canAutomationPlaceItem(itemStack: ItemStack): Boolean { + return super.canAutomationPlaceItem(itemStack) && canAutomationPlaceItem.canAutomationPlaceItem(this, itemStack) + } + + override fun canAutomationTakeItem(desired: Int): Boolean { + return super.canAutomationTakeItem(desired) && canAutomationTakeItem.canAutomationTakeItem(this, desired) + } + + override fun modifyAutomationPlaceCount(itemStack: ItemStack): Int { + return modifyAutomationPlaceCount.modifyAutomationPlaceCount(this, itemStack) + } + + override fun modifyAutomationExtractionCount(desired: Int): Int { + return modifyAutomationExtractionCount.modifyAutomationExtractionCount(this, desired) + } + } + + override fun create(container: SlottedContainer, index: Int): FilteredContainerSlot { + return Instance(container, index) + } + } + + companion object { + private val LOGGER = LogManager.getLogger() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt new file mode 100644 index 000000000..50fe19366 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/slotted/SlottedContainer.kt @@ -0,0 +1,436 @@ +package ru.dbotthepony.mc.otm.container.slotted + +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet +import net.minecraft.core.HolderLookup +import net.minecraft.core.registries.BuiltInRegistries +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.ListTag +import net.minecraft.nbt.NbtOps +import net.minecraft.nbt.Tag +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.neoforged.neoforge.common.util.INBTSerializable +import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kommons.util.Either +import ru.dbotthepony.mc.otm.container.BitmapTrackingContainer +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.IAutomatedContainer +import ru.dbotthepony.mc.otm.container.IAutomatedContainerSlot +import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.balance +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.data.codec.minRange +import java.util.BitSet +import java.util.function.Predicate +import kotlin.reflect.KClass + +/** + * Concrete container implementation, operating on slots. + * + * Due to inflexible constraints, it can not be inherited, and usually it is not required since + * most logic is embedded inside [ContainerSlot]. + * + * If you specifically need to inherit container implementation, use [EnhancedContainer], which was designed to be inherited. + */ +class SlottedContainer( + slots: Collection>, + private val stillValid: Predicate, + private val globalChangeListeners: Array +) : BitmapTrackingContainer(), IAutomatedContainer, INBTSerializable { + interface ISlotGroup : List { + /** + * @see IAutomatedContainer.addItem + */ + fun addItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): ItemStack + + /** + * @see IAutomatedContainer.consumeItem + */ + fun consumeItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): Boolean + + /** + * @see IAutomatedContainer.fullyAddItem + */ + fun fullyAddItem(stack: ItemStack, onlyIntoExisting: Boolean = false, popTime: Int? = null, ignoreFilters: Boolean = false): Boolean + + fun balance() + } + + private inner class SlotGroup : ISlotGroup, AbstractList() { + val slots = IntArrayList() + + override val size: Int + get() = slots.size + + override fun get(index: Int): T { + return this@SlottedContainer.slots[slots.getInt(index)] as T + } + + override fun addItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): ItemStack { + return this@SlottedContainer.addItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters) + } + + override fun consumeItem(stack: ItemStack, simulate: Boolean, onlyIntoExisting: Boolean, popTime: Int?, ignoreFilters: Boolean): Boolean { + return this@SlottedContainer.consumeItem(stack, simulate, slots, onlyIntoExisting, popTime, ignoreFilters) + } + + override fun fullyAddItem( + stack: ItemStack, + onlyIntoExisting: Boolean, + popTime: Int?, + ignoreFilters: Boolean + ): Boolean { + return this@SlottedContainer.fullyAddItem(stack, slots, onlyIntoExisting, popTime, ignoreFilters) + } + + override fun balance() { + return this@SlottedContainer.balance(slots) + } + } + + class SingleTag(val clazz: KClass) { + override fun toString(): String { + return "SlottedContainer.SingleTag[${System.identityHashCode(this).toString(16)}@${clazz.qualifiedName}]" + } + } + + class MultiTag(val clazz: KClass) { + override fun toString(): String { + return "SlottedContainer.MultiTag[${System.identityHashCode(this).toString(16)}@${clazz.qualifiedName}]" + } + } + + private val sets = HashMap, SlotGroup<*>>() + private val singular = HashMap, ContainerSlot>() + private val slots: Array + + init { + val itr = slots.iterator() + this.slots = Array(slots.size) { index -> + val (mark, provider) = itr.next() + val slot = provider.create(this, index) + mark?.map({ require(singular.put(it, slot) == null) { "Duplicate Slot tag: $it" } }, { sets.computeIfAbsent(it) { SlotGroup() }.slots.add(index) }) + slot + } + } + + operator fun get(tag: MultiTag): ISlotGroup { + return sets[tag] as ISlotGroup? ?: throw NoSuchElementException("Container does not contain $tag") + } + + operator fun get(tag: SingleTag): T { + return singular[tag] as T? ?: throw NoSuchElementException("Container does not contain $tag") + } + + override val hasFilterableSlots: Boolean = this.slots.any { it is IFilteredContainerSlot } + private var suppressListeners = false + + override fun clearContent() { + bitmap.clear() + suppressListeners = true + + try { + slots.forEach { it.remove() } + notifyChanged() + } finally { + suppressListeners = false + } + } + + override fun containerSlot(slot: Int): ContainerSlot { + return slots[slot] + } + + fun notifyChanged() { + if (suppressListeners) return + globalChangeListeners.forEach { it.run() } + } + + fun notifyChanged(slot: Int) { + notifyChanged() + bitmap[slot] = slots[slot].isNotEmpty + } + + // called by outside code (vanilla and other unaware mods) + override fun setChanged() { + suppressListeners = true + var hasChanges = false + + try { + slots.forEach { hasChanges = it.observeChanges() || hasChanges } + } finally { + suppressListeners = false + + if (hasChanges) + notifyChanged() + } + } + + override fun getContainerSize(): Int { + return slots.size + } + + override fun stillValid(player: Player): Boolean { + return stillValid.test(player) + } + + private data class LegacySerializedItem(val item: ItemStack, val slot: Int) { + companion object { + val CODEC: Codec = RecordCodecBuilder.create { + it.group( + ItemStack.OPTIONAL_CODEC.fieldOf("item").forGetter { it.item }, + Codec.INT.minRange(0).fieldOf("slot").forGetter { it.slot }, + ).apply(it, SlottedContainer::LegacySerializedItem) + } + } + } + + private data class LegacySerializedFilter(val item: Item, val slot: Int) { + companion object { + val CODEC: Codec = RecordCodecBuilder.create { + it.group( + BuiltInRegistries.ITEM.byNameCodec().fieldOf("item").forGetter { it.item }, + Codec.INT.minRange(0).fieldOf("slot").forGetter { it.slot }, + ).apply(it, SlottedContainer::LegacySerializedFilter) + } + } + } + + private data class LegacySerializedState( + val items: List, + val filters: List + ) { + companion object { + val CODEC: Codec = RecordCodecBuilder.create { + it.group( + Codec.list(LegacySerializedItem.CODEC).fieldOf("items").forGetter { it.items }, + Codec.list(LegacySerializedFilter.CODEC).fieldOf("filters").forGetter { it.filters }, + ).apply(it, SlottedContainer::LegacySerializedState) + } + } + } + + private val lostItems = ArrayList() + private var provider: HolderLookup.Provider? = null + + fun takeLostItems(): List { + if (lostItems.isEmpty()) + return listOf() + + val provider = provider?.createSerializationContext(NbtOps.INSTANCE) ?: return listOf() + val result = ArrayList() + + for (entry in lostItems) { + if ("item" in entry) { + ItemStack.OPTIONAL_CODEC.decode(provider, entry["item"]) + .ifError { LOGGER.warn("Unable to deserialize 'lost' item: ${it.message()}") } + .ifSuccess { if (it.first.isNotEmpty) result.add(it.first) } + } + } + + lostItems.clear() + return result + } + + fun takeLostItems(player: ServerPlayer) { + for (stack in takeLostItems()) { + player.inventory.add(stack) + + if (stack.isNotEmpty) { + player.drop(stack, false, false) + } + } + } + + override fun serializeNBT(provider: HolderLookup.Provider): ListTag { + return ListTag().also { + for (slot in slots) { + it.add(slot.serializeNBT(provider)) + } + + it.addAll(lostItems) + } + } + + override fun deserializeNBT(provider: HolderLookup.Provider, nbt: Tag) { + lostItems.clear() + slots.forEach { it.clear() } + bitmap.clear() + + if (nbt is CompoundTag) { + // legacy container + LegacySerializedState.CODEC.decode(provider.createSerializationContext(NbtOps.INSTANCE), nbt) + .resultOrPartial { LOGGER.error("Error deserializing container: $it") } + .ifPresent { + val (items, filters) = it.first + + // excessive items will be lost + for ((item, slot) in items) { + if (slot in 0 until containerSize) { + slots[slot].item = item + bitmap[slot] = item.isNotEmpty + } else if (item.isNotEmpty) { + ItemStack.CODEC.encodeStart(provider.createSerializationContext(NbtOps.INSTANCE), item) + .ifError { LOGGER.warn("Unable to serialize 'lost' item: ${it.message()}") } + .ifSuccess { s -> + this.provider = provider + + lostItems.add(CompoundTag().also { + it["item"] = s + }) + } + } + } + + for ((filter, slot) in filters) { + if (slot in 0 until containerSize) { + val getSlot = slots[slot] + + if (getSlot is IFilteredContainerSlot) { + getSlot.filter = ItemFilter.item(filter) + } + } + } + } + } else if (nbt is ListTag) { + // normal container + for ((i, element) in nbt.withIndex()) { + if (element !is CompoundTag) { + LOGGER.error("Deserializing mattery container: Expected compound tag at $i, got $element") + continue + } + + if (i in 0 until containerSize) { + slots[i].deserializeNBT(provider, element) + bitmap[i] = slots[i].isNotEmpty + } else { + lostItems.add(element) + this.provider = provider + } + } + } else { + LOGGER.error("Unable to deserialize mattery container, expected CompoundTag or ListTag, got $nbt") + } + + notifyChanged() + } + + fun interface SlotProvider { + fun create(container: SlottedContainer, index: Int): T + } + + data class MarkedSlotProvider(val mark: Either, MultiTag>?, val provider: SlotProvider) + + class Builder { + private val slots = ArrayList>() + private var stillValid = Predicate { true } + private val globalChangeListeners = ArrayList() + private val seenSingleTags = ObjectOpenHashSet>() + + fun add(slot: SlotProvider<*>): Builder { + slots.add(MarkedSlotProvider(null, slot)) + return this + } + + fun add(tag: SingleTag, slot: SlotProvider): Builder { + require(seenSingleTags.add(tag)) { "Duplicate slot tag: $tag" } + slots.add(MarkedSlotProvider(Either.left(tag), slot)) + return this + } + + fun add(tag: MultiTag, slot: SlotProvider): Builder { + slots.add(MarkedSlotProvider(Either.right(tag), slot)) + return this + } + + fun add(amount: Int, provider: SlotProvider<*>): Builder { + for (i in 0 until amount) + slots.add(MarkedSlotProvider(null, provider)) + + return this + } + + fun add(amount: Int, tag: MultiTag, provider: SlotProvider): Builder { + for (i in 0 until amount) + slots.add(MarkedSlotProvider(Either.right(tag), provider)) + + return this + } + + fun stillValid(predicate: Predicate): Builder { + this.stillValid = predicate + return this + } + + fun onChanged(listener: Runnable): Builder { + globalChangeListeners.add(listener) + return this + } + + fun copy(): Builder { + val copy = Builder() + copy.slots.addAll(slots) + copy.globalChangeListeners.addAll(globalChangeListeners) + copy.stillValid = stillValid + return copy + } + + fun build(): SlottedContainer { + return SlottedContainer(slots, stillValid, globalChangeListeners.toTypedArray()) + } + } + + companion object { + private val LOGGER = LogManager.getLogger() + + inline fun tag(): SingleTag { + return SingleTag(T::class) + } + + inline fun tagList(): MultiTag { + return MultiTag(T::class) + } + + @Deprecated("Consider using EnhancedContainer", replaceWith = ReplaceWith("EnhancedContainer(size)", "ru.dbotthepony.mc.otm.container.EnhancedContainer")) + fun simple(size: Int): SlottedContainer { + return Builder().add(size, ::ContainerSlot).build() + } + + @Deprecated("Consider using EnhancedContainer", replaceWith = ReplaceWith("EnhancedContainer.withListener(size, listener)", "ru.dbotthepony.mc.otm.container.EnhancedContainer")) + fun simple(size: Int, listener: Runnable): SlottedContainer { + return Builder() + .add(size, ::ContainerSlot) + .onChanged(listener) + .build() + } + + fun simple(size: Int, provider: SlotProvider<*>): SlottedContainer { + return Builder().add(size, provider).build() + } + + fun simple(size: Int, provider: SlotProvider<*>, listener: Runnable): SlottedContainer { + return Builder() + .add(size, provider) + .onChanged(listener) + .build() + } + + fun filtered(size: Int): SlottedContainer { + return Builder().add(size, ::FilteredContainerSlot).build() + } + + fun filtered(size: Int, listener: Runnable): SlottedContainer { + return Builder() + .add(size, ::FilteredContainerSlot) + .onChanged(listener) + .build() + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt index 1dd6bad9c..c5af49249 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/util/Iterators.kt @@ -1,55 +1,63 @@ package ru.dbotthepony.mc.otm.container.util import net.minecraft.world.Container +import net.minecraft.world.inventory.Slot import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.container.IContainerSlot -import ru.dbotthepony.mc.otm.container.IMatteryContainer +import ru.dbotthepony.mc.otm.container.IEnhancedContainer import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.isNotEmpty -class SimpleContainerSlot(override val slot: Int, override val container: Container) : IContainerSlot { - init { - require(slot in 0 until container.containerSize) { "Slot out of bounds: $slot (container: $container with size ${container.containerSize})" } - } - - override fun getFilter(): Item? { - return null - } - - override fun setFilter(filter: Item?): Boolean { - return false - } -} - fun Container.containerSlot(slot: Int): IContainerSlot { - if (this is IMatteryContainer) { + if (this is IEnhancedContainer<*>) { return containerSlot(slot) } else { - return SimpleContainerSlot(slot, this) + return IContainerSlot.Simple(slot, this) } } -operator fun Container.iterator() = iterator(true) +/** + * Returns [IContainerSlot] only if this container is [IEnhancedContainer] + */ +fun Container.containerSlotOrNull(slot: Int): IContainerSlot? { + if (this is IEnhancedContainer<*>) { + return containerSlot(slot) + } else { + return null + } +} -fun Container.iterator(nonEmpty: Boolean): Iterator { - if (this is IMatteryContainer) { - return iterator(nonEmpty) - } else if (nonEmpty) { +fun Slot.containerSlot(): IContainerSlot { + return container.containerSlot(slotIndex) +} + +fun Slot.containerSlotOrNull(): IContainerSlot? { + return container.containerSlotOrNull(slotIndex) +} + +operator fun Container.iterator(): Iterator { + if (this is IEnhancedContainer<*>) { + return iterator() + } else { return (0 until containerSize).iterator().map { this[it] }.filter { it.isNotEmpty } - } else { - return (0 until containerSize).iterator().map { this[it] } } } -fun Container.slotIterator(nonEmpty: Boolean = true): Iterator { - if (this is IMatteryContainer) { - return slotIterator(nonEmpty) - } else if (nonEmpty) { - return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { SimpleContainerSlot(it, this) } +fun Container.slotIterator(): Iterator { + if (this is IEnhancedContainer<*>) { + return slotIterator() } else { - return (0 until containerSize).iterator().map { SimpleContainerSlot(it, this) } + return (0 until containerSize).iterator().map { IContainerSlot.Simple(it, this) } + } +} + +fun Container.nonEmptySlotIterator(): Iterator { + if (this is IEnhancedContainer<*>) { + return nonEmptySlotIterator() + } else { + return (0 until containerSize).iterator().filter { this[it].isNotEmpty }.map { IContainerSlot.Simple(it, this) } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index e1e0b3427..c099f54d9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -3,6 +3,9 @@ package ru.dbotthepony.mc.otm.core +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.Scheduler import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMultimap @@ -10,7 +13,6 @@ import com.google.common.collect.ImmutableSet import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive -import it.unimi.dsi.fastutil.ints.IntList import it.unimi.dsi.fastutil.objects.ObjectComparators import net.minecraft.Util import net.minecraft.core.BlockPos @@ -29,7 +31,6 @@ import net.minecraft.network.chat.contents.TranslatableContents import net.minecraft.resources.ResourceKey import net.minecraft.resources.ResourceLocation import net.minecraft.tags.TagKey -import net.minecraft.util.RandomSource import net.minecraft.world.entity.Entity import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack @@ -60,18 +61,17 @@ import java.io.InputStream import java.io.OutputStream import java.lang.ref.Reference import java.math.BigInteger +import java.time.Duration import java.util.* import java.util.concurrent.Callable import java.util.concurrent.Future import java.util.function.Consumer import java.util.function.Supplier -import java.util.random.RandomGenerator import java.util.stream.Stream import java.util.stream.StreamSupport import kotlin.NoSuchElementException +import kotlin.enums.EnumEntries import kotlin.jvm.optionals.getOrNull -import kotlin.math.ln -import kotlin.math.sqrt import kotlin.reflect.KProperty operator fun RecipeInput.get(index: Int): ItemStack = getItem(index) @@ -189,43 +189,6 @@ fun > T.prev(values: Array): T { return values[next] } -fun IntArray.shuffle(random: RandomSource): IntArray { - for (i in lastIndex downTo 1) { - val j = random.nextInt(i + 1) - val copy = this[i] - this[i] = this[j] - this[j] = copy - } - - return this -} - -fun L.shuffle(random: RandomSource): L { - for (i in lastIndex downTo 1) { - val j = random.nextInt(i + 1) - set(j, set(i, getInt(j))) - } - - return this -} - -fun > L.shuffle(random: RandomSource): L { - Util.shuffle(this, random) - return this -} - -fun List.random(random: RandomGenerator): T { - if (isEmpty()) - throw NoSuchElementException("This list is empty") - return get(random.nextInt(size)) -} - -fun List.random(random: RandomSource): T { - if (isEmpty()) - throw NoSuchElementException("This list is empty") - return get(random.nextInt(size)) -} - inline fun immutableList(size: Int, initializer: (index: Int) -> T): ImmutableList { require(size >= 0) { "Invalid list size $size" } @@ -307,6 +270,10 @@ fun OutputStream.writeItemType(value: Item) { writeVarIntLE(BuiltInRegistries.ITEM.getId(value)) } +fun > FriendlyByteBuf.readEnum(entries: EnumEntries): E { + return entries[readVarInt()] +} + fun FriendlyByteBuf.readType(registry: IdMap): T { val id = readInt() return registry.byId(id) ?: throw NoSuchElementException("No such entry with ID $id") @@ -635,58 +602,15 @@ infix fun FluidStack.isNotSameAs(other: FluidStack): Boolean { return !FluidStack.isSameFluidSameComponents(this, other) && amount == other.amount } -data class DoublePair(val first: Double, val second: Double) - -fun RandomSource.nextUUID(): UUID { - return UUID(nextLong(), nextLong()) +fun SimpleCache(size: Long, freshness: Duration): Cache { + return Caffeine.newBuilder() + .maximumSize(size) + .scheduler(Scheduler.systemScheduler()) + .executor(Util.backgroundExecutor()) + .expireAfterWrite(freshness) + .build() } -// normal distribution via Marsaglia polar method -fun RandomGenerator.nextNormalDoubles(stddev: Double, mean: Double): DoublePair { - var rand1: Double - var rand2: Double - var distSqr: Double - - do { - rand1 = 2.0 * nextDouble() - 1.0 - rand2 = 2.0 * nextDouble() - 1.0 - distSqr = rand1 * rand1 + rand2 * rand2 - } while (distSqr >= 1.0 || distSqr == 0.0) - - val mapping = sqrt(-2.0 * ln(distSqr) / distSqr) - - return DoublePair( - rand1 * mapping * stddev + mean, - rand2 * mapping * stddev + mean - ) -} - -// All Mojang's random sources use MarsagliaPolarGaussian for generating normal distributed doubles -fun RandomSource.nextNormalDoubles(stddev: Double, mean: Double): DoublePair { - return DoublePair( - nextGaussian() * stddev + mean, - nextGaussian() * stddev + mean - ) -} - -fun RandomSource.nextNormalDouble(stddev: Double, mean: Double): Double { - return nextGaussian() * stddev + mean -} - -fun RandomSource.nextFloat(min: Float, max: Float): Float { - if (this is RandomGenerator) - return nextFloat(min, max) - - require(max >= min) { "Min is bigger than max: $min vs $max" } - if (min == max) return min - return min + nextFloat() * (max - min) -} - -fun RandomSource.nextDouble(min: Double, max: Double): Double { - if (this is RandomGenerator) - return nextDouble(min, max) - - require(max >= min) { "Min is bigger than max: $min vs $max" } - if (min == max) return min - return min + nextDouble() * (max - min) +fun SimpleCache(freshness: Duration): Cache { + return SimpleCache(16384L, freshness) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/RandomUtils.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/RandomUtils.kt new file mode 100644 index 000000000..cf7da7c9a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/RandomUtils.kt @@ -0,0 +1,221 @@ +package ru.dbotthepony.mc.otm.core + +import it.unimi.dsi.fastutil.ints.IntList +import net.minecraft.Util +import net.minecraft.util.RandomSource +import ru.dbotthepony.mc.otm.core.math.Decimal +import java.math.BigInteger +import java.util.* +import java.util.random.RandomGenerator +import kotlin.experimental.and +import kotlin.math.ln +import kotlin.math.sqrt + +data class DoublePair(val first: Double, val second: Double) + +fun RandomSource.nextUUID(): UUID { + return UUID(nextLong(), nextLong()) +} + +// normal distribution via Marsaglia polar method +fun RandomGenerator.nextNormalDoubles(stddev: Double, mean: Double): DoublePair { + var rand1: Double + var rand2: Double + var distSqr: Double + + do { + rand1 = 2.0 * nextDouble() - 1.0 + rand2 = 2.0 * nextDouble() - 1.0 + distSqr = rand1 * rand1 + rand2 * rand2 + } while (distSqr >= 1.0 || distSqr == 0.0) + + val mapping = sqrt(-2.0 * ln(distSqr) / distSqr) + + return DoublePair( + rand1 * mapping * stddev + mean, + rand2 * mapping * stddev + mean + ) +} + +// All Mojang's random sources use MarsagliaPolarGaussian for generating normal distributed doubles +fun RandomSource.nextNormalDoubles(stddev: Double, mean: Double): DoublePair { + return DoublePair( + nextGaussian() * stddev + mean, + nextGaussian() * stddev + mean + ) +} + +fun RandomSource.nextNormalDouble(stddev: Double, mean: Double): Double { + return nextGaussian() * stddev + mean +} + +fun RandomSource.nextFloat(min: Float, max: Float): Float { + if (this is RandomGenerator) + return nextFloat(min, max) + + require(max >= min) { "Min is bigger than max: $min vs $max" } + if (min == max) return min + return min + nextFloat() * (max - min) +} + +fun RandomSource.nextDouble(min: Double, max: Double): Double { + if (this is RandomGenerator) + return nextDouble(min, max) + + require(max >= min) { "Min is bigger than max: $min vs $max" } + if (min == max) return min + return min + nextDouble() * (max - min) +} + +fun IntArray.shuffle(random: RandomSource): IntArray { + for (i in lastIndex downTo 1) { + val j = random.nextInt(i + 1) + val copy = this[i] + this[i] = this[j] + this[j] = copy + } + + return this +} + +fun L.shuffle(random: RandomSource): L { + for (i in lastIndex downTo 1) { + val j = random.nextInt(i + 1) + set(j, set(i, getInt(j))) + } + + return this +} + +fun > L.shuffle(random: RandomSource): L { + Util.shuffle(this, random) + return this +} + +fun List.random(random: RandomGenerator): T { + if (isEmpty()) + throw NoSuchElementException("This list is empty") + return get(random.nextInt(size)) +} + +fun List.random(random: RandomSource): T { + if (isEmpty()) + throw NoSuchElementException("This list is empty") + return get(random.nextInt(size)) +} + +class RandomByteSource(private val source: RandomSource) { + private val bytes = ByteArray(8) + private var i = 7 + + fun next(): Byte { + if (++i == 8) { + i = 0 + + var generate = source.nextLong() + + for (i in 0 .. 7) { + bytes[i] = generate.toByte() + generate = generate ushr 8 + } + } + + return bytes[i] + } + + fun next(bound: Int): Byte { + require(bound > 0) { "Bound must be positive" } + val m = bound - 1 + var r = next().toInt().and(0xFF) + + if (bound and m == 0) { + r = r and m + } else { + var u = r ushr 1 + + while (true) { + r = u % bound + + if (u + m - r < 0) { + u = next().toInt().and(0xFF).ushr(1) + } else { + break + } + } + } + + return r.toByte() + } + + fun next(bytes: ByteArray) { + for (i in bytes.indices) { + bytes[i] = next() + } + } +} + +/** + * Uniformely distributed [Decimal] value on [0,[bound]) range + * + * If [round] is `true`, will only return integers + */ +fun RandomSource.nextDecimal(bound: Decimal, round: Boolean = false): Decimal { + if (round) + require(bound >= Decimal.ONE) { "Bound must be 1 or bigger, $bound given" } + else + require(bound > Decimal.ZERO) { "Bound must be positive, $bound given" } + + require(bound.isFinite) { "Bound must be finite" } + + val bytes = RandomByteSource(this) + + if (round) { + val thisBytes = bound.whole.toByteArray() + val generateBytes = ByteArray(thisBytes.size) + bytes.next(generateBytes) + + generateBytes[0] = generateBytes[0].and(127) + + if (generateBytes[0] >= thisBytes[0]) { + generateBytes[0] = bytes.next(thisBytes[0].toInt().and(127)) + } + + return Decimal(BigInteger(generateBytes)) + } else { + val thisBytes = bound.mag.toByteArray() + val generateBytes = ByteArray(thisBytes.size) + bytes.next(generateBytes) + + generateBytes[0] = generateBytes[0].and(127) + + if (generateBytes[0] >= thisBytes[0]) { + generateBytes[0] = bytes.next(thisBytes[0].toInt().and(127)) + } + + return Decimal.raw(BigInteger(generateBytes)) + } +} + +/** + * Uniformely distributed [Decimal] value on [[origin],[bound]) range + * + * If [round] is `true`, will only return integers + */ +fun RandomSource.nextDecimal(origin: Decimal, bound: Decimal, round: Boolean = false): Decimal { + require(origin < bound) { "Origin must be less than bound: $origin < $bound" } + require(origin.isFinite) { "Origin must be finite" } + require(bound.isFinite) { "Bound must be finite" } + + return origin + nextDecimal(bound - origin, round) +} + +/** + * Uniformely distributed [Decimal] value on [0,1) range + */ +fun RandomSource.nextDecimal(): Decimal { + return nextDecimal(Decimal.ZERO, Decimal.ONE) +} + +fun RandomSource.nextVariance(value: Decimal, round: Boolean = false): Decimal { + return nextDecimal(-value / 2, value / 2, round) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TooltipList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TooltipList.kt index ea819668e..0ab1d1033 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TooltipList.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TooltipList.kt @@ -23,7 +23,7 @@ import ru.dbotthepony.mc.otm.client.ShiftPressedCond import ru.dbotthepony.mc.otm.client.isShiftDown import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.config.ClientConfig -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.mapPresent @@ -112,7 +112,7 @@ class TooltipList { val registry = context.registries() if (registry != null) { - val container = MatteryContainer(1) + val container = SlottedContainer.filtered(1) tag.map(batteryKey) { it: Tag -> container.deserializeNBT(registry, it) } if (!container[0].isEmpty) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/IntRange2Set.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/IntRange2Set.kt new file mode 100644 index 000000000..7e1588613 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/IntRange2Set.kt @@ -0,0 +1,206 @@ +package ru.dbotthepony.mc.otm.core.collect + +import it.unimi.dsi.fastutil.HashCommon +import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator +import it.unimi.dsi.fastutil.ints.IntCollection +import it.unimi.dsi.fastutil.ints.IntComparator +import it.unimi.dsi.fastutil.ints.IntIterators +import it.unimi.dsi.fastutil.ints.IntSet +import it.unimi.dsi.fastutil.ints.IntSortedSet + +class IntRange2Set private constructor(private val first: Int, private val last: Int) : IntSortedSet { + constructor(range: IntRange) : this(range.first, range.last) { + require(range.step == 1) { "Provided range has non-standard step of ${range.step}" } + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun add(element: Int): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun addAll(c: IntCollection?): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun addAll(elements: Collection): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun clear() { + throw UnsupportedOperationException() + } + + override fun iterator(fromElement: Int): IntBidirectionalIterator { + if (isEmpty() || fromElement > last) + return IntIterators.EMPTY_ITERATOR + else if (fromElement <= first) + return IntIterators.fromTo(first, last + 1) + else + return IntIterators.fromTo(fromElement, last + 1) + } + + override fun iterator(): IntBidirectionalIterator { + if (isEmpty()) + return IntIterators.EMPTY_ITERATOR + + return IntIterators.fromTo(first, last + 1) + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun remove(k: Int): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun removeAll(c: IntCollection?): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun removeAll(elements: Collection): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun retainAll(c: IntCollection?): Boolean { + throw UnsupportedOperationException() + } + + @Deprecated("Not supported", level = DeprecationLevel.ERROR) + override fun retainAll(elements: Collection): Boolean { + throw UnsupportedOperationException() + } + + override fun contains(key: Int): Boolean { + return key in first .. last + } + + override fun containsAll(c: IntCollection): Boolean { + return c.ktIterator().all { it in first .. last } + } + + override fun containsAll(elements: Collection): Boolean { + return elements.all { it in first .. last } + } + + override fun isEmpty(): Boolean { + return last < first + } + + override fun toArray(a: IntArray?): IntArray { + if (a == null || a.size < size) + return toIntArray() + + var index = 0 + + for (i in first .. last) { + a[index++] = i + } + + return a + } + + override fun toIntArray(): IntArray { + if (isEmpty()) + return EMPTY_ARRAY + + return IntArray(last - first + 1) { first + it } + } + + override fun comparator(): IntComparator? { + return null + } + + override fun subSet(fromElement: Int, toElement: Int): IntSortedSet { + if (fromElement <= first && toElement > last) + return this + else if (fromElement <= first) + return IntRange2Set(first, toElement - 1) + else if (toElement > last) + return IntRange2Set(fromElement, last) + else + return EMPTY + } + + override fun headSet(toElement: Int): IntSortedSet { + if (isEmpty() || toElement <= first) + return EMPTY + else if (toElement < last) + return this + else + return IntRange2Set(first, toElement - 1) + } + + override fun tailSet(fromElement: Int): IntSortedSet { + if (isEmpty() || fromElement > last) + return EMPTY + else if (fromElement < first) + return this + else + return IntRange2Set(fromElement, last) + } + + override fun firstInt(): Int { + if (isEmpty()) + throw NoSuchElementException("Range is empty") + + return first + } + + override fun lastInt(): Int { + if (isEmpty()) + throw NoSuchElementException("Range is empty") + + return last + } + + override val size: Int + get() = if (last < first) 0 else last - first + 1 + + override fun toString(): String { + if (isEmpty()) + return "IntRange2Set[EMPTY]" + + return "IntRange2Set[$first .. $last]" + } + + override fun equals(other: Any?): Boolean { + return this === other || + other is IntRange2Set && (isEmpty() == other.isEmpty() || first == other.first && last == other.last) || + other is IntSet && other.size == size && containsAll(other) || + other is Set<*> && other.size == size && other.all { it is Int && it in first .. last } + } + + override fun hashCode(): Int { + if (isEmpty()) + return EMPTY_HASH + + return HashCommon.murmurHash3(last - first) + } + + companion object { + /** + * Returns set containing values beginning from [from] (inclusive) to [to] (inclusive) + */ + fun closed(from: Int, to: Int): IntRange2Set { + if (from > to) + return EMPTY + + return IntRange2Set(from, to) + } + + /** + * Returns set containing values beginning from [from] (inclusive) to [to] (exclusive) + */ + fun openEnded(from: Int, to: Int): IntRange2Set { + return closed(from, to - 1) + } + + private val EMPTY_ARRAY = IntArray(0) + val EMPTY = IntRange2Set(0, -1) + private val EMPTY_HASH = HashCommon.murmurHash3(0) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt index 469b95915..3b73cab8a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/Iterables.kt @@ -1,7 +1,10 @@ package ru.dbotthepony.mc.otm.core.collect +import it.unimi.dsi.fastutil.ints.IntCollection import it.unimi.dsi.fastutil.ints.IntIterable import it.unimi.dsi.fastutil.ints.IntIterator +import it.unimi.dsi.fastutil.ints.IntSortedSet +import ru.dbotthepony.mc.otm.core.math.Decimal fun IntRange.asIterable(): IntIterable { return IntIterable { @@ -22,3 +25,43 @@ fun IntRange.asIterable(): IntIterable { } } } + +fun IntCollection.ktIterator(): kotlin.collections.IntIterator { + return object : kotlin.collections.IntIterator() { + private val parent = this@ktIterator.intIterator() + + override fun nextInt(): Int { + return parent.nextInt() + } + + override fun hasNext(): Boolean { + return parent.hasNext() + } + } +} + +fun IntSortedSet.ktIterator(fromElement: Int): kotlin.collections.IntIterator { + return object : kotlin.collections.IntIterator() { + private val parent = this@ktIterator.iterator(fromElement) + + override fun nextInt(): Int { + return parent.nextInt() + } + + override fun hasNext(): Boolean { + return parent.hasNext() + } + } +} + +inline fun Iterable.sumOfDecimal(selector: (T) -> Decimal): Decimal { + var result = Decimal.ZERO + forEach { result += selector(it) } + return result +} + +inline fun Iterator.sumOfDecimal(selector: (T) -> Decimal): Decimal { + var result = Decimal.ZERO + forEach { result += selector(it) } + return result +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt index 4c4289757..284cb53be 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt @@ -1,17 +1,9 @@ package ru.dbotthepony.mc.otm.core.math import net.minecraft.nbt.ByteArrayTag -import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.StringTag import net.minecraft.nbt.Tag import net.minecraft.network.FriendlyByteBuf -import net.minecraft.util.RandomSource -import net.neoforged.neoforge.common.ModConfigSpec -import ru.dbotthepony.mc.otm.config.ObservedConfigValue -import ru.dbotthepony.mc.otm.core.util.readVarIntLE -import ru.dbotthepony.mc.otm.core.util.writeVarIntLE -import java.io.InputStream -import java.io.OutputStream import java.math.BigDecimal import java.math.BigInteger import java.math.MathContext @@ -47,6 +39,8 @@ sealed class Decimal : Number(), Comparable { */ abstract val fractional: BigInteger + abstract internal val mag: BigInteger + /** * *Signed* normalized (-1,1) fractional part of this Decimal, as [Float] */ @@ -189,7 +183,7 @@ sealed class Decimal : Number(), Comparable { return toBigDecmial().divide(divisor.toBigDecmial(), PERCENTAGE_CONTEXT).toFloat() } - private class Regular(val mag: BigInteger, marker: Nothing?) : Decimal() { + private class Regular(override val mag: BigInteger, marker: Nothing?) : Decimal() { constructor(value: BigInteger) : this(value * PRECISION_POW_BI, null) constructor(value: BigDecimal) : this(value.setScale(PRECISION, RoundingMode.HALF_UP).unscaledValue(), null) constructor(value: Float) : this(BigDecimal.valueOf(value.toDouble())) @@ -711,6 +705,9 @@ sealed class Decimal : Number(), Comparable { } private object PositiveInfinity : Decimal() { + override val mag: BigInteger + get() = throw UnsupportedOperationException() + private fun readResolve(): Any = PositiveInfinity override val isInfinite: Boolean @@ -960,6 +957,9 @@ sealed class Decimal : Number(), Comparable { } private object NegativeInfinity : Decimal() { + override val mag: BigInteger + get() = throw UnsupportedOperationException() + private fun readResolve(): Any = NegativeInfinity override val isInfinite: Boolean @@ -1203,6 +1203,9 @@ sealed class Decimal : Number(), Comparable { } private object Zero : Decimal() { + override val mag: BigInteger + get() = BigInteger.ZERO + private fun readResolve(): Any = Zero override val isInfinite: Boolean @@ -1603,28 +1606,6 @@ sealed class Decimal : Number(), Comparable { } } -fun FriendlyByteBuf.readDecimal() = Decimal.read(this) -fun FriendlyByteBuf.writeDecimal(value: Decimal) = value.write(this) - -fun InputStream.readDecimal(): Decimal { - val size = readVarIntLE() - require(size >= 0) { "Negative payload size: $size" } - val bytes = ByteArray(size) - read(bytes) - return Decimal.fromByteArray(bytes) -} - -fun OutputStream.writeDecimal(value: Decimal) { - val bytes = value.toByteArray() - writeVarIntLE(bytes.size) - write(bytes) -} - -fun CompoundTag.getDecimal(key: String) = Decimal.deserializeNBT(this[key]) -fun CompoundTag.putDecimal(key: String, value: Decimal) = put(key, value.serializeNBT()) - -operator fun CompoundTag.set(key: String, value: Decimal) = putDecimal(key, value) - fun Float.toDecimal() = Decimal(this) fun Double.toDecimal() = Decimal(this) fun Int.toDecimal() = Decimal(this) @@ -1633,70 +1614,3 @@ fun Short.toDecimal() = Decimal(this) fun Long.toDecimal() = Decimal(this) fun Decimal.toDecimal() = this -class DecimalConfigValue( - parent: ModConfigSpec.ConfigValue, - val minimum: Decimal? = null, - val maximum: Decimal? = null, -) : ObservedConfigValue(parent) { - override fun fromString(value: String): Decimal? { - try { - val parsed = Decimal(value) - - if (minimum != null && minimum > parsed) { - return minimum - } else if (maximum != null && maximum < parsed) { - return maximum - } - - return parsed - } catch (err: java.lang.NumberFormatException) { - return null - } - } - - override fun toString(value: Decimal): Pair { - if (minimum != null && minimum > value) { - return minimum.toString() to minimum - } else if (maximum != null && maximum < value) { - return maximum.toString() to maximum - } - - return value.toString() to value - } -} - -private fun ModConfigSpec.Builder.commentRange(minimum: Decimal?, maximum: Decimal?) { - if (minimum != null && maximum != null) { - comment("Range: $minimum ~ $maximum") - } else if (minimum != null) { - comment("Range: >= $minimum") - } else if (maximum != null) { - comment("Range: <= $maximum") - } -} - -fun ModConfigSpec.Builder.defineDecimal(path: String, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue { - commentRange(minimum, maximum) - comment("Default: $defaultValue") - return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum) -} - -fun ModConfigSpec.Builder.defineDecimal(path: List, defaultValue: Decimal, minimum: Decimal? = null, maximum: Decimal? = null): DecimalConfigValue { - commentRange(minimum, maximum) - comment("Default: $defaultValue") - return DecimalConfigValue(define(path, defaultValue.toString()), minimum, maximum) -} - -fun RandomSource.nextDecimal(min: Decimal, max: Decimal, round: Boolean = false): Decimal { - val value = nextDouble() - - return if (round) { - Decimal((min + (max - min) * value).whole) - } else { - min + (max - min) * value - } -} - -fun RandomSource.nextVariance(value: Decimal, round: Boolean = false): Decimal { - return nextDecimal(-value / 2, value / 2, round) -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt index 7dca62760..48101e58f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/EuclidMath.kt @@ -435,6 +435,14 @@ operator fun Vec3i.times(int: Int): Vec3i = this.multiply(int) operator fun Direction.times(int: Int): Vec3i = this.normal.multiply(int) operator fun Vec3i.times(double: Double): Vector = Vector(x * double, y * double, z * double) +operator fun Vector.plus(direction: Vec3i): Vector = Vector(x + direction.x, y + direction.y, z + direction.z) +operator fun Vector.plus(direction: Direction): Vector = plus(direction.normal) +operator fun Vector.minus(direction: Vec3i): Vector = Vector(x - direction.x, y - direction.y, z - direction.z) +operator fun Vector.minus(direction: Direction): Vector = minus(direction.normal) + +operator fun Vec3i.plus(direction: Vector): Vector = Vector(x + direction.x, y + direction.y, z + direction.z) +operator fun Vec3i.minus(direction: Vector): Vector = Vector(x - direction.x, y - direction.y, z - direction.z) + fun Vec3.toIntVector() = Vec3i(x.toInt(), y.toInt(), z.toInt()) fun Vec3.toBlockPos() = BlockPos(x.toInt(), y.toInt(), z.toInt()) fun Vec3.roundToIntVector() = Vec3i(x.roundToInt(), y.roundToInt(), z.roundToInt()) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt index 9387c88e2..eaec30837 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/nbt/CompoundTagExt.kt @@ -20,6 +20,7 @@ import net.minecraft.nbt.ShortTag import net.minecraft.nbt.StringTag import net.minecraft.nbt.Tag import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.util.readBinaryJson import ru.dbotthepony.mc.otm.core.util.writeBinaryJson import java.util.UUID @@ -59,6 +60,16 @@ inline fun CompoundTag.map(key: String, consumer: (T) -> R return null } +inline fun CompoundTag.map(key: String, consumer: (A0, T) -> R, arg0: A0): R? { + val tag = get(key) + + if (tag is T) { + return consumer(arg0, tag) + } + + return null +} + inline fun CompoundTag.mapPresent(key: String, consumer: (T) -> R): R? { val tag = get(key) @@ -115,3 +126,7 @@ fun CompoundTag.getJson(key: String, sizeLimit: NbtAccounter = NbtAccounter(1 sh fun CompoundTag.getBoolean(index: String, orElse: Boolean): Boolean { return (this[index] as? NumericTag)?.asInt?.let { it > 0 } ?: orElse } + +fun CompoundTag.getDecimal(key: String) = Decimal.deserializeNBT(this[key]) +fun CompoundTag.putDecimal(key: String, value: Decimal) = put(key, value.serializeNBT()) +operator fun CompoundTag.set(key: String, value: Decimal) = putDecimal(key, value) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt index c1aa44fc4..a97d9f517 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/FriendlyStreams.kt @@ -5,12 +5,14 @@ import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.NbtAccounter import net.minecraft.nbt.NbtIo +import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack import net.minecraft.world.level.material.Fluid import net.neoforged.neoforge.fluids.FluidStack +import ru.dbotthepony.mc.otm.core.math.Decimal import java.io.* import java.math.BigDecimal import java.math.BigInteger @@ -238,3 +240,20 @@ fun OutputStream.writeBinaryString(input: String) { writeVarIntLE(bytes.size) write(bytes) } + +fun FriendlyByteBuf.readDecimal() = Decimal.read(this) +fun FriendlyByteBuf.writeDecimal(value: Decimal) = value.write(this) + +fun InputStream.readDecimal(): Decimal { + val size = readVarIntLE() + require(size >= 0) { "Negative payload size: $size" } + val bytes = ByteArray(size) + read(bytes) + return Decimal.fromByteArray(bytes) +} + +fun OutputStream.writeDecimal(value: Decimal) { + val bytes = value.toByteArray() + writeVarIntLE(bytes.size) + write(bytes) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LootTableUtils.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LootTableUtils.kt index ae0a0a1be..8821690ee 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LootTableUtils.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/LootTableUtils.kt @@ -9,6 +9,7 @@ import net.minecraft.world.item.ItemStack import net.minecraft.world.level.storage.loot.LootParams import net.minecraft.world.level.storage.loot.LootTable import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.container.IEnhancedContainer import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.core.isNotEmpty @@ -105,9 +106,14 @@ private fun shuffle(items: MutableList, emptySlotCount: Int, random: fun LootTable.fill(params: LootParams, random: RandomSource, container: Container, rounds: Int = 1) { val emptySlots = IntArrayList() - for (i in 0 until container.containerSize) - if (container[i].isEmpty) + if (container is IEnhancedContainer<*>) { + for (i in container.emptySlotIndexIterator()) emptySlots.add(i) + } else { + for (i in 0 until container.containerSize) + if (container[i].isEmpty) + emptySlots.add(i) + } emptySlots.shuffle(random) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt index 238f42d84..9f2e734b6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt @@ -7,6 +7,7 @@ import net.minecraft.core.HolderLookup.Provider import net.minecraft.nbt.ByteTag import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.DoubleTag +import net.minecraft.nbt.EndTag import net.minecraft.nbt.FloatTag import net.minecraft.nbt.IntTag import net.minecraft.nbt.LongTag @@ -297,7 +298,7 @@ class Savetables : INBTSerializable { for (entry in entries) { val value = entry.serializeNBT(registry) - if (value != null) + if (value != null && value !is EndTag) nbt[entry.name] = value } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/FlywheelMaterials.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/FlywheelMaterials.kt index c2cbf6141..50388d03a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/FlywheelMaterials.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/FlywheelMaterials.kt @@ -38,8 +38,8 @@ import ru.dbotthepony.mc.otm.core.ResourceLocation import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.readDecimal -import ru.dbotthepony.mc.otm.core.math.writeDecimal +import ru.dbotthepony.mc.otm.core.util.readDecimal +import ru.dbotthepony.mc.otm.core.util.writeDecimal import ru.dbotthepony.mc.otm.core.readBlockType import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.util.formatPower diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/world/DecimalProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/world/DecimalProvider.kt index 67a993e4e..fc733d3f3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/world/DecimalProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/world/DecimalProvider.kt @@ -8,7 +8,7 @@ import net.minecraft.util.RandomSource import net.neoforged.bus.api.IEventBus import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.nextDecimal +import ru.dbotthepony.mc.otm.core.nextDecimal import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.registry.MBuiltInRegistries import ru.dbotthepony.mc.otm.registry.MDeferredRegister diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/Ext.kt index f9ec9cdbf..3071cfeaf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/entity/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/entity/Ext.kt @@ -1,8 +1,10 @@ package ru.dbotthepony.mc.otm.entity +import net.minecraft.core.BlockPos import net.minecraft.network.syncher.EntityDataAccessor import net.minecraft.network.syncher.SynchedEntityData import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.player.Player import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -17,3 +19,7 @@ class SynchedEntityDataDelegate(private val type: EntityDataAccessor, priv } fun SynchedEntityData.delegate(type: EntityDataAccessor) = SynchedEntityDataDelegate(type, this) + +fun Player.checkCanInteract(pos: BlockPos): Boolean { + return mayInteract(level(), pos) && canInteractWithBlock(pos, 1.0) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt index c23e65dee..26250d8a3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt @@ -13,7 +13,7 @@ import net.neoforged.neoforge.event.entity.player.ItemEntityPickupEvent import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.drive.DrivePool import ru.dbotthepony.mc.otm.capability.drive.ItemMatteryDrive -import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.isServerThread import ru.dbotthepony.mc.otm.registry.CapabilitiesRegisterListener @@ -55,18 +55,18 @@ class PortableCondensationDriveItem(capacity: Int) : Item(Properties().stacksTo( }, this) } - fun getFilterSettings(item: ItemStack): ItemFilter { + fun getFilterSettings(item: ItemStack): ItemFilterSet { return item.getOrDefault(MDataComponentTypes.ITEM_FILTER, EMPTY_FILTER) } - fun setFilterSettings(item: ItemStack, filter: ItemFilter) { + fun setFilterSettings(item: ItemStack, filter: ItemFilterSet) { item.set(MDataComponentTypes.ITEM_FILTER, filter) } @Suppress("unused") companion object { const val MAX_FILTERS = 4 * 3 - private val EMPTY_FILTER = ItemFilter(MAX_FILTERS) + private val EMPTY_FILTER = ItemFilterSet.EMPTY internal fun onPickupEvent(event: ItemEntityPickupEvent.Pre) { if (event.itemEntity.owner != null && event.itemEntity.owner != event.player && event.itemEntity.age < 200 || event.itemEntity.item.isEmpty) { @@ -83,9 +83,9 @@ class PortableCondensationDriveItem(capacity: Int) : Item(Properties().stacksTo( var doBreak = false stack.getCapability(MatteryCapability.CONDENSATION_DRIVE)?.let { - val filter = stack[MDataComponentTypes.ITEM_FILTER] ?: ItemFilter.EMPTY + val filter = stack[MDataComponentTypes.ITEM_FILTER] ?: ItemFilterSet.EMPTY - if (filter.match(event.itemEntity.item)) { + if (filter.test(event.itemEntity.item)) { val copy = event.itemEntity.item.copy() val remaining = (it as ItemMatteryDrive).insertStack(event.itemEntity.item, false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt index 5e2ba1f97..270b8ab3f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt @@ -38,11 +38,10 @@ import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.getDecimal -import ru.dbotthepony.mc.otm.core.math.readDecimal -import ru.dbotthepony.mc.otm.core.math.set -import ru.dbotthepony.mc.otm.core.math.writeDecimal +import ru.dbotthepony.mc.otm.core.nbt.getDecimal +import ru.dbotthepony.mc.otm.core.util.readDecimal import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.writeDecimal import ru.dbotthepony.mc.otm.core.nextUUID import ru.dbotthepony.mc.otm.core.util.formatPower import ru.dbotthepony.mc.otm.isClientThread diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt index db22662eb..e8060499d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/matter/MatterDustItem.kt @@ -17,7 +17,8 @@ import net.minecraft.world.level.storage.loot.LootContext import net.minecraft.world.level.storage.loot.functions.LootItemFunction import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType import ru.dbotthepony.mc.otm.config.ItemsConfig -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.IEnhancedContainer +import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.matter.IMatterItem @@ -65,7 +66,7 @@ class MatterDustItem : Item(Properties().stacksTo(64)), IMatterItem { return MatterValue(value, 1.0) } - fun moveIntoContainer(matterValue: Decimal, container: MatteryContainer, mainSlot: Int, stackingSlot: Int): Decimal { + fun moveIntoContainer(matterValue: Decimal, container: IEnhancedContainer<*>, mainSlot: Int, stackingSlot: Int): Decimal { @Suppress("name_shadowing") var matterValue = matterValue diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt index 666ffbac2..bf4d16672 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/EnergySwordItem.kt @@ -29,9 +29,9 @@ import ru.dbotthepony.mc.otm.player.matteryPlayer import ru.dbotthepony.mc.otm.core.ResourceLocation import ru.dbotthepony.mc.otm.core.damageType import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.DecimalConfigValue -import ru.dbotthepony.mc.otm.core.math.defineDecimal -import ru.dbotthepony.mc.otm.core.math.nextVariance +import ru.dbotthepony.mc.otm.config.DecimalConfigValue +import ru.dbotthepony.mc.otm.config.defineDecimal +import ru.dbotthepony.mc.otm.core.nextVariance import ru.dbotthepony.mc.otm.core.otmRandom import ru.dbotthepony.mc.otm.core.util.WriteOnce import ru.dbotthepony.mc.otm.item.MatteryItem diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/FallingSunItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/FallingSunItem.kt index d4f636c9e..0b7042e3b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/FallingSunItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/weapon/FallingSunItem.kt @@ -29,9 +29,9 @@ import ru.dbotthepony.mc.otm.player.matteryPlayer import ru.dbotthepony.mc.otm.core.ResourceLocation import ru.dbotthepony.mc.otm.core.damageType import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.DecimalConfigValue -import ru.dbotthepony.mc.otm.core.math.defineDecimal -import ru.dbotthepony.mc.otm.core.math.nextVariance +import ru.dbotthepony.mc.otm.config.DecimalConfigValue +import ru.dbotthepony.mc.otm.config.defineDecimal +import ru.dbotthepony.mc.otm.core.nextVariance import ru.dbotthepony.mc.otm.core.otmRandom import ru.dbotthepony.mc.otm.core.util.WriteOnce import ru.dbotthepony.mc.otm.item.MatteryItem diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterValue.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterValue.kt index 17fa4e5c2..5c87223a9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterValue.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/IMatterValue.kt @@ -2,8 +2,8 @@ package ru.dbotthepony.mc.otm.matter import net.minecraft.network.FriendlyByteBuf import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.readDecimal -import ru.dbotthepony.mc.otm.core.math.writeDecimal +import ru.dbotthepony.mc.otm.core.util.readDecimal +import ru.dbotthepony.mc.otm.core.util.writeDecimal import ru.dbotthepony.mc.otm.core.util.readDouble import ru.dbotthepony.mc.otm.core.util.writeDouble import java.io.InputStream diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt index 32ae1bd51..001216ca1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/MatterManager.kt @@ -77,7 +77,8 @@ import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive import ru.dbotthepony.mc.otm.client.isShiftDown import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.config.ClientConfig -import ru.dbotthepony.mc.otm.container.MatteryCraftingContainer +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.IEnhancedCraftingContainer import ru.dbotthepony.mc.otm.container.util.stream import ru.dbotthepony.mc.otm.core.ResourceLocation import ru.dbotthepony.mc.otm.core.TextComponent @@ -499,7 +500,7 @@ object MatterManager { height = it.value.ingredients.size.coerceAtLeast(height) } - val container = MatteryCraftingContainer(width, height) + val container = IEnhancedCraftingContainer.Wrapper(EnhancedContainer.Simple(width * height), width, height) val realIngredients = ArrayList>() for (c in it.value.ingredients.indices) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt index 0eca65659..74e068363 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/ExopackInventoryMenu.kt @@ -28,7 +28,7 @@ import ru.dbotthepony.mc.otm.network.ExopackSlotPacket class ExopackInventoryMenu(val capability: MatteryPlayer) : MatteryMenu(null, CONTAINER_ID, capability.ply.inventory) { val craftingGrid: CraftingContainer - val craftingSlots: List + val craftingSlots: List init { if (capability.isExopackCraftingUpgraded) { @@ -37,10 +37,10 @@ class ExopackInventoryMenu(val capability: MatteryPlayer) : MatteryMenu(null, CO craftingGrid = TransientCraftingContainer(this, 2, 2) } - val builder = ImmutableList.builder() + val builder = ImmutableList.builder() for (i in 0 until craftingGrid.containerSize) { - builder.add(MatterySlot(craftingGrid, i)) + builder.add(MatteryMenuSlot(craftingGrid, i)) } craftingSlots = builder.build() @@ -98,16 +98,16 @@ class ExopackInventoryMenu(val capability: MatteryPlayer) : MatteryMenu(null, CO } } - val furnaceInputs: List = capability.smelters.map { - object : MatterySlot(it.input, 0) { + val furnaceInputs: List = capability.smelters.map { + object : MatteryMenuSlot(it.input, 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return super.mayPlace(itemStack) && capability.isExopackSmeltingInstalled } } } - val furnaceOutputs: List = capability.smelters.map { - object : OutputSlot(it.output, 0, onTake = { popFurnaceExp() }) { + val furnaceOutputs: List = capability.smelters.map { + object : OutputMenuSlot(it.output, 0, onTake = { popFurnaceExp() }) { override fun mayPickup(player: Player): Boolean { return super.mayPickup(player) && capability.isExopackSmeltingInstalled } @@ -122,7 +122,7 @@ class ExopackInventoryMenu(val capability: MatteryPlayer) : MatteryMenu(null, CO addStorageSlot(furnaceOutputs, condition = furnaceMenuOpenState) } - val enderChestSlots: List + val enderChestSlots: List val enderChestOpenState = InstantBooleanInput(this) val playerEnderSortSettings = IItemStackSortingSettings.inputs(this, capability.enderSortingSettings) val sortEnderChest: SortInput? @@ -130,12 +130,12 @@ class ExopackInventoryMenu(val capability: MatteryPlayer) : MatteryMenu(null, CO init { if (capability.isExopackEnderAccessInstalled) { enderChestSlots = makeSlots(player.enderChestInventory) { a, b -> - MatterySlot(a, b).also { + MatteryMenuSlot(a, b).also { addStorageSlot(it, condition = enderChestOpenState) } } - sortEnderChest = SortInput(player.enderChestInventory, playerEnderSortSettings) + sortEnderChest = SortInput(this, player.enderChestInventory, playerEnderSortSettings) } else { enderChestSlots = listOf() sortEnderChest = null @@ -177,7 +177,7 @@ class ExopackInventoryMenu(val capability: MatteryPlayer) : MatteryMenu(null, CO if (!player.level().isClientSide) { for (slot in craftingGrid.slotIterator()) { - val leftover = moveItemStackToSlots(slot.item, playerInventorySlots) + val leftover = QuickMoveInput.moveItemStackToSlots(slot.item, playerInventorySlots) if (!leftover.isEmpty) { player.drop(leftover, true) @@ -205,11 +205,11 @@ class ExopackInventoryMenu(val capability: MatteryPlayer) : MatteryMenu(null, CO if (slotIndex == craftingResultSlot.index) { val item = craftingResultSlot.item - val leftover = moveItemStackToSlots(item, playerInventorySlots, simulate = true) + val leftover = QuickMoveInput.moveItemStackToSlots(item, playerInventorySlots, simulate = true) if (leftover.isEmpty) { val copy = item.copy() - moveItemStackToSlots(item, playerInventorySlots, simulate = false) + QuickMoveInput.moveItemStackToSlots(item, playerInventorySlots, simulate = false) item.count = 0 craftingResultSlot.onTake(ply, copy) return copy diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt index 3568f9ecf..4a27b275c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -3,8 +3,6 @@ package ru.dbotthepony.mc.otm.menu import com.google.common.collect.ImmutableList import com.mojang.datafixers.util.Pair import it.unimi.dsi.fastutil.bytes.ByteArrayList -import it.unimi.dsi.fastutil.ints.IntArrayList -import it.unimi.dsi.fastutil.ints.IntCollection import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ReferenceArrayList @@ -15,7 +13,6 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.util.RandomSource -import net.minecraft.world.Container import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu @@ -33,17 +30,19 @@ import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.player.matteryPlayer import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorSlots import ru.dbotthepony.mc.otm.compat.curios.curiosSlots import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot -import ru.dbotthepony.mc.otm.container.IMatteryContainer -import ru.dbotthepony.mc.otm.container.computeSortedIndices -import ru.dbotthepony.mc.otm.container.sortWithIndices +import ru.dbotthepony.mc.otm.container.IEnhancedContainer +import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull import ru.dbotthepony.mc.otm.core.ResourceLocation import ru.dbotthepony.mc.otm.core.collect.ConditionalSet import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.entity.checkCanInteract import ru.dbotthepony.mc.otm.core.util.GJRAND64RandomSource import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget @@ -58,6 +57,7 @@ import ru.dbotthepony.mc.otm.network.readByteListUnbounded import ru.dbotthepony.mc.otm.network.syncher.SynchableGroup import ru.dbotthepony.mc.otm.network.wrap import ru.dbotthepony.mc.otm.network.writeByteListUnbounded +import ru.dbotthepony.mc.otm.player.IPlayerInventorySlot import java.util.* import java.util.function.BooleanSupplier import java.util.function.Consumer @@ -66,7 +66,7 @@ import java.util.function.Predicate data class PlayerSlot(val functional: A, val cosmetic: B? = null) data class EquipmentSlots( - val armorSlots: List>, + val armorSlots: List>, val curiosSlots: List> ) @@ -87,7 +87,7 @@ abstract class MatteryMenu( private val _playerInventorySlots = ArrayList() private val _playerHotbarSlots = ArrayList() private val _playerCombinedInventorySlots = ArrayList() - private val _exopackChargeSlots = ArrayList() + private val _exopackChargeSlots = ArrayList() private val playerInputs = ArrayList>() @@ -162,16 +162,6 @@ abstract class MatteryMenu( } } - inner class SortInput(val container: Container, val settings: IItemStackSortingSettings) { - val input = PlayerInput(MatteryStreamCodec.Collection(StreamCodecs.VAR_INT, ::IntArrayList)) { - container.sortWithIndices(it) - } - - fun clientInput() { - input.accept(container.computeSortedIndices(settings.actualComparator)) - } - } - fun oneWayInput(allowSpectators: Boolean = false, handler: () -> Unit): PlayerInput { return PlayerInput(StreamCodecs.NOTHING, allowSpectators) { handler.invoke() @@ -188,7 +178,7 @@ abstract class MatteryMenu( fun intInput(allowSpectators: Boolean = false, handler: (Int) -> Unit) = PlayerInput(StreamCodecs.INT, allowSpectators, handler) /** - * hotbar + inventory + Exopack (in this order) + * inventory + Exopack + hotbar (in this order) */ val playerInventorySlots: List = Collections.unmodifiableList(_playerInventorySlots) @@ -202,7 +192,7 @@ abstract class MatteryMenu( */ val playerCombinedInventorySlots: List = Collections.unmodifiableList(_playerCombinedInventorySlots) - val exopackChargeSlots: List = Collections.unmodifiableList(_exopackChargeSlots) + val exopackChargeSlots: List = Collections.unmodifiableList(_exopackChargeSlots) val exopackPowerLevel = ProfiledLevelGaugeWidget>(mSynchronizer, player.matteryPlayer.exopackEnergy) @@ -224,7 +214,7 @@ abstract class MatteryMenu( protected var inventorySlotIndexStart = 0 protected var inventorySlotIndexEnd = 0 - open inner class InventorySlot(container: Container, index: Int, addFilter: Boolean = false) : UserFilteredSlot(container, index, 0, 0) { + open inner class InventorySlot(container: IEnhancedContainer, index: Int) : UserFilteredMenuSlot(container, index, 0, 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return !isInventorySlotLocked(index) && super.mayPlace(itemStack) } @@ -237,28 +227,15 @@ abstract class MatteryMenu( private set init { - val mattery = player.matteryPlayer - - if (addFilter) { - val mContainer = container as IMatteryContainer - - filter = Delegate.Of( - getter = { mContainer.getSlotFilter(slotIndex) }, - setter = nullableItemInput(true) { mContainer.setSlotFilter(slotIndex, it) }::accept - ) - } - - if (mattery.hasExopack) { - chargeFlag = Delegate.Of( - getter = { slotIndex in mattery.slotsChargeFlag }, - setter = booleanInput(true) { if (mattery.hasExopack) { if (it) mattery.slotsChargeFlag.add(slotIndex) else mattery.slotsChargeFlag.remove(slotIndex) } }::accept - ) + if (player.matteryPlayer.hasExopack) { + val slot = container.containerSlot(index) + chargeFlag = Delegate.Of(slot::shouldCharge) } } } - open inner class EquipmentSlot(container: Container, index: Int, val type: net.minecraft.world.entity.EquipmentSlot) : InventorySlot(container, index) { - constructor(type: net.minecraft.world.entity.EquipmentSlot) : this(inventory, 34 + type.ordinal, type) + open inner class EquipmentMenuSlot(container: IEnhancedContainer, index: Int, val type: net.minecraft.world.entity.EquipmentSlot) : InventorySlot(container, index) { + constructor(type: net.minecraft.world.entity.EquipmentSlot) : this(player.matteryPlayer.wrappedInventory, 34 + type.ordinal, type) override fun setByPlayer(newItem: ItemStack, oldItem: ItemStack) { inventory.player.onEquipItem(type, oldItem, newItem) @@ -288,7 +265,7 @@ abstract class MatteryMenu( autoCreateInventoryFrame = autoFrame - offhandSlot = object : InventorySlot(inventory, 40) { + offhandSlot = object : InventorySlot(player.matteryPlayer.wrappedInventory, 40) { override fun setByPlayer(newItem: ItemStack, oldItem: ItemStack) { inventory.player.onEquipItem(net.minecraft.world.entity.EquipmentSlot.OFFHAND, oldItem, newItem) super.setByPlayer(newItem, oldItem) @@ -305,9 +282,9 @@ abstract class MatteryMenu( for (i in 0 until if (mattery.hasExopack) mattery.combinedInventory.containerSize else mattery.wrappedItemInventory.containerSize) { if (i in Inventory.INVENTORY_SIZE until player.inventory.containerSize) continue - val slot = InventorySlot(mattery.combinedInventory, i, true) + val slot = InventorySlot(mattery.combinedInventory, i) - _playerInventorySlots.add(slot) + // _playerInventorySlots.add(slot) if (i <= 8) _playerHotbarSlots.add(slot) @@ -319,14 +296,17 @@ abstract class MatteryMenu( addSlot(slot) } + _playerInventorySlots.addAll(_playerCombinedInventorySlots) + _playerInventorySlots.addAll(_playerHotbarSlots) + if (mattery.hasExopack) { - _exopackChargeSlots.add(BatterySlot(mattery.exopackEnergy.parent, 0).also { mapQuickMoveToExternal(it); mapQuickMoveToInventory(it); addSlot(it) }) + _exopackChargeSlots.add(BatteryMenuSlot(mattery.exopackEnergy.parent, 0, direction = FlowDirection.OUTPUT).also { mapQuickMoveToExternal(it); mapQuickMoveToInventory(it); addSlot(it) }) for (i in 0 until mattery.exopackChargeSlots.containerSize) - _exopackChargeSlots.add(ChargeSlot(mattery.exopackChargeSlots, i).also { mapQuickMoveToExternal(it); mapQuickMoveToInventory(it); addSlot(it) }) + _exopackChargeSlots.add(BatteryMenuSlot(mattery.exopackChargeSlots, i, direction = FlowDirection.INPUT).also { mapQuickMoveToExternal(it); mapQuickMoveToInventory(it); addSlot(it) }) } - sortInventoryInput = SortInput(mattery.inventoryAndExopackNoHotbar, playerSortSettings) + sortInventoryInput = SortInput(this, mattery.inventoryAndExopackNoHotbar, playerSortSettings) } private var broadcastOnce = false @@ -368,13 +348,10 @@ abstract class MatteryMenu( override fun stillValid(player: Player): Boolean { if (tile == null) return true - if (player.level().getBlockEntity(tile.blockPos) !== tile) { + if (player.level().getBlockEntity(tile.blockPos) !== tile) return false - } - val pos = tile.blockPos - - return player.distanceToSqr(pos.x.toDouble() + 0.5, pos.y.toDouble() + 0.5, pos.z.toDouble() + 0.5) <= 64.0 + return player.checkCanInteract(tile.blockPos) } fun syncCarried() { @@ -395,21 +372,8 @@ abstract class MatteryMenu( if (!seenSlots.add(pSlot)) return pSlot - if (pSlot is UserFilteredSlot && !pSlot.hasSetFilter) { - val container = pSlot.container - - val input: PlayerInput - val field: Delegate - - if (container is IMatteryContainer) { - input = PlayerInput(StreamCodecs.ITEM_TYPE_NULLABLE, handler = { container.setSlotFilter(pSlot.slotIndex, it) }) - field = mSynchronizer.add(delegate = { container.getSlotFilter(pSlot.slotIndex) }, StreamCodecs.ITEM_TYPE_NULLABLE) - } else { - input = PlayerInput(StreamCodecs.ITEM_TYPE_NULLABLE, handler = { throw UnsupportedOperationException() }) - field = mSynchronizer.add(delegate = { null }, StreamCodecs.ITEM_TYPE_NULLABLE) - } - - pSlot.filter = Delegate.Of(getter = field::get, setter = input::accept) + if (pSlot is MatteryMenuSlot) { + pSlot.setupNetworkControls(this) } return super.addSlot(pSlot) @@ -490,25 +454,10 @@ abstract class MatteryMenu( val copy = slot.item.copy() var any = false - if (target.any { it.any { it is UserFilteredSlot && it.filter != null } }) { - for (collection in target) { - if (moveItemStackTo(slot, collection, onlyFiltered = true)) { - any = true - - if (!slot.hasItem()) { - return copy - } - } - } - } - for (collection in target) { - if (moveItemStackTo(slot, collection)) { + if (QuickMoveInput.moveItemStackTo(ply, slot, collection)) { any = true - - if (!slot.hasItem()) { - return copy - } + if (!slot.hasItem()) return copy } } @@ -524,14 +473,14 @@ abstract class MatteryMenu( return stack } - return moveItemStackToSlots(stack, _playerInventorySlots, simulate = simulate) + return QuickMoveInput.moveItemStackToSlots(stack, _playerInventorySlots, simulate = simulate) } override fun canTakeItemForPickAll(itemStack: ItemStack, slot: Slot): Boolean { - if (slot is EquipmentSlot) + if (slot is EquipmentMenuSlot) return false - return super.canTakeItemForPickAll(itemStack, slot) && (slot !is MatterySlot || slot.canTakeItemForPickAll()) && !slot.isCurioSlot + return super.canTakeItemForPickAll(itemStack, slot) && (slot !is MatteryMenuSlot || slot.canTakeItemForPickAll()) && !slot.isCurioSlot } override fun moveItemStackTo( @@ -550,95 +499,6 @@ abstract class MatteryMenu( return true } - fun moveItemStackTo( - source: Slot, - slots: Collection, - onlyFiltered: Boolean = false - ): Boolean { - val remainder = moveItemStackToSlots(source.item, slots, onlyFiltered = onlyFiltered) - - if (remainder.count == source.item.count) { - return false - } - - val copy = source.item.copy() - - if (remainder.isEmpty) { - source.setByPlayer(ItemStack.EMPTY) - source.onTake(player, copy) - } else { - copy.count = source.item.count - remainder.count - source.item.count = remainder.count - source.onTake(player, copy) - } - - return true - } - - fun moveItemStackToSlots(item: ItemStack, slots: Collection, simulate: Boolean = false, onlyFiltered: Boolean = false): ItemStack { - if (item.isEmpty) { - return ItemStack.EMPTY - } - - val copy = item.copy() - - // first pass - stack with existing slots - if (copy.isStackable) { - for (slot in slots) { - if (onlyFiltered && (slot !is UserFilteredSlot || !slot.test(item))) { - continue - } else if (!onlyFiltered && slot is UserFilteredSlot && !slot.test(item)) { - continue - } - - val limit = slot.getMaxStackSize(copy) - - if (limit > slot.item.count && slot.mayPlace(item) && ItemStack.isSameItemSameComponents(slot.item, copy)) { - val newCount = (slot.item.count + copy.count).coerceAtMost(limit) - val diff = newCount - slot.item.count - copy.count -= diff - - if (!simulate) { - slot.item.count += diff - slot.setChanged() - } - - if (copy.isEmpty) { - return copy - } - } - } - } - - // second pass - drop stack into first free slot - for (slot in slots) { - if (onlyFiltered && (slot !is UserFilteredSlot || slot.filter == null || slot.filter!!.get() != item.item)) { - continue - } else if (!onlyFiltered && slot is UserFilteredSlot && slot.filter != null && slot.filter!!.get() != null && slot.filter!!.get() != item.item) { - continue - } - - val limit = slot.getMaxStackSize(copy) - - if (!slot.hasItem() && slot.mayPlace(item)) { - val newCount = copy.count.coerceAtMost(limit) - - if (!simulate) { - slot.setByPlayer(copy.copy().also { it.count = newCount }) - slot.setChanged() - } - - copy.count -= newCount - - if (copy.isEmpty) { - return copy - } - } - } - - return copy - } - fun moveItemStackToSlots(item: ItemStack, initialSlot: Int, finalSlot: Int, inverse: Boolean = false, simulate: Boolean = false): ItemStack { if (initialSlot > finalSlot) { return item @@ -648,29 +508,19 @@ abstract class MatteryMenu( require(finalSlot < slots.size) { "Final slot $finalSlot is bigger than total size of array of ${slots.size}" } val slots = ArrayList(finalSlot - initialSlot + 1) - var filters = false for (i in (if (inverse) finalSlot downTo initialSlot else initialSlot .. finalSlot)) { - val slot = slots[i] - slots.add(slot) - - if (slot is InventorySlot && slot.filter != null) { - filters = true - } + slots.add(this.slots[i]) } - if (filters) { - return moveItemStackToSlots(moveItemStackToSlots(item, slots, simulate, onlyFiltered = true), slots, simulate, onlyFiltered = false) - } - - return moveItemStackToSlots(item, slots, simulate) + return QuickMoveInput.moveItemStackToSlots(item, slots, simulate) } - private var armorSlots: ImmutableList>? = null + private var armorSlots: ImmutableList>? = null private var curiosSlots: ImmutableList>? = null private val equipmentSlots = ArrayList() - fun makeArmorSlots(mapMoveToExternal: Boolean = false): List> { + fun makeArmorSlots(mapMoveToExternal: Boolean = false): List> { if (armorSlots != null) { return armorSlots!! } @@ -678,10 +528,10 @@ abstract class MatteryMenu( val cosmetic = player.cosmeticArmorSlots return ImmutableList.of( - PlayerSlot(EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.HEAD), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.HEAD)), - PlayerSlot(EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.CHEST), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.CHEST)), - PlayerSlot(EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.LEGS), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.LEGS)), - PlayerSlot(EquipmentSlot(net.minecraft.world.entity.EquipmentSlot.FEET), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.FEET)), + PlayerSlot(EquipmentMenuSlot(net.minecraft.world.entity.EquipmentSlot.HEAD), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.HEAD)), + PlayerSlot(EquipmentMenuSlot(net.minecraft.world.entity.EquipmentSlot.CHEST), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.CHEST)), + PlayerSlot(EquipmentMenuSlot(net.minecraft.world.entity.EquipmentSlot.LEGS), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.LEGS)), + PlayerSlot(EquipmentMenuSlot(net.minecraft.world.entity.EquipmentSlot.FEET), cosmetic?.get(net.minecraft.world.entity.EquipmentSlot.FEET)), ).also { armorSlots = it diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryPoweredMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryPoweredMenu.kt index 8f0e8ca81..a8853c826 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryPoweredMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryPoweredMenu.kt @@ -6,6 +6,7 @@ import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import net.minecraft.world.SimpleContainer import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback abstract class MatteryPoweredMenu protected constructor( @@ -15,7 +16,7 @@ abstract class MatteryPoweredMenu protected constructor( tile: MatteryPoweredBlockEntity? = null ) : MatteryMenu(menuType, containerID, inventory, tile) { val energyWidget = LevelGaugeWidget(this, tile?.energy) - val batterySlot = BatterySlot(tile?.batteryContainer ?: SimpleContainer(1), 0) + val batterySlot = BatteryMenuSlot(tile?.batteryContainer ?: SlottedContainer.filtered(1), 0) val redstoneConfig = EnumInputWithFeedback(this) init { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/QuickMoveInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/QuickMoveInput.kt new file mode 100644 index 000000000..3d49f5cd8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/QuickMoveInput.kt @@ -0,0 +1,273 @@ +package ru.dbotthepony.mc.otm.menu + +import net.minecraft.network.chat.Component +import net.minecraft.world.entity.player.Player +import net.minecraft.world.inventory.Slot +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.client.render.Widgets18 +import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite +import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.container.ItemStackKey +import ru.dbotthepony.mc.otm.container.asKey +import ru.dbotthepony.mc.otm.container.asKeyOrNull + +class QuickMoveInput(private val menu: MatteryMenu, val from: Collection, val to: Collection, val mode: Mode, val dontTouchFilteredSlots: Boolean = true) { + /** + * slots with items come first + */ + object HasItemComparator : Comparator { + override fun compare(a: Slot, b: Slot): Int { + val hasItemA = a.item.isNotEmpty + val hasItemB = b.item.isNotEmpty + + return hasItemB.compareTo(hasItemA) + } + } + + /** + * slots with filters come first + */ + object HasFilterComparator : Comparator { + override fun compare(a: Slot, b: Slot): Int { + val slotA = a.containerSlotOrNull() + val slotB = b.containerSlotOrNull() + + val hasFilterA = slotA is IFilteredContainerSlot && slotA.filter.hasRules + val hasFilterB = slotB is IFilteredContainerSlot && slotB.filter.hasRules + + return hasFilterB.compareTo(hasFilterA) + } + } + + enum class Mode(val iconFromStorage: AbstractMatterySprite, val iconToStorage: AbstractMatterySprite) { + RESTOCK( + Widgets18.RESTOCK_FROM_STORAGE, + Widgets18.RESTOCK_TO_STORAGE + ) { + override fun move(from: Collection, to: Collection, player: Player, dontTouchFilteredSlots: Boolean) { + if (from.isEmpty() || to.isEmpty()) return + val (_, itemsFrom) = computeSlotLists(from, dontTouchFilteredSlots) + val (_, itemsTo, filteredTo) = computeSlotLists(to, false) + + val intersect: Collection + + if (filteredTo.isNotEmpty()) + intersect = itemsFrom.keys + else if (itemsFrom.size < itemsTo.size) + intersect = itemsFrom.keys.filter { it in itemsTo.keys } + else + intersect = itemsTo.keys.filter { it in itemsFrom.keys } + + for (key in intersect) { + val slotsTo = ArrayList(itemsTo[key] ?: listOf()) + slotsTo.addAll(0, filteredTo) + val slotsFrom = itemsFrom[key]!! + + slotsFrom.forEach { moveItemStackTo(player, it, slotsTo) } + } + } + }, + + RESTOCK_WITH_MOVE( + Widgets18.RESTOCK_WITH_MOVE_FROM_STORAGE, + Widgets18.RESTOCK_WITH_MOVE_TO_STORAGE + ) { + override fun move(from: Collection, to: Collection, player: Player, dontTouchFilteredSlots: Boolean) { + if (from.isEmpty() || to.isEmpty()) return + val (_, itemsFrom) = computeSlotLists(from, dontTouchFilteredSlots) + val (emptyTo, itemsTo, filteredTo) = computeSlotLists(to, false) + + val intersect: Collection + + if (filteredTo.isNotEmpty()) + intersect = itemsFrom.keys + else if (itemsFrom.size < itemsTo.size) + intersect = itemsFrom.keys.filter { it in itemsTo.keys } + else + intersect = itemsTo.keys.filter { it in itemsFrom.keys } + + for (key in intersect) { + val slotsTo = ArrayList(itemsTo[key] ?: listOf()).also { it.addAll(0, filteredTo) } + if (slotsTo.isEmpty()) continue + prioritySortSlotsInPlace(slotsTo, key.asItemStack()) + + val slotsFrom = itemsFrom[key]!! + + slotsFrom.removeIf { moveItemStackTo(player, it, slotsTo, sort = false); it.item.isEmpty } + var moveAny = false + slotsFrom.forEach { moveAny = moveItemStackTo(player, it, emptyTo, sort = false) || moveAny } + if (moveAny) emptyTo.removeIf { it.item.isNotEmpty } + } + } + }, + + MOVE( + Widgets18.MOVE_EVERYTHING_FROM_STORAGE, + Widgets18.MOVE_EVERYTHING_TO_STORAGE + ) { + override fun move(from: Collection, to: Collection, player: Player, dontTouchFilteredSlots: Boolean) { + if (from.isEmpty() || to.isEmpty()) return + val toSorted = prioritySortSlots(to) + + from.forEach { + val slot = it.containerSlotOrNull() + + if (!dontTouchFilteredSlots || slot !is IFilteredContainerSlot || !slot.filter.hasRules) + moveItemStackTo(player, it, toSorted, sort = false) + } + } + }; + + abstract fun move(from: Collection, to: Collection, player: Player, dontTouchFilteredSlots: Boolean = true) + + val textFromStorage: Component get() { + return TranslatableComponent("otm.gui.quickmove_from.${name.lowercase()}") + } + + val textToStorage: Component get() { + return TranslatableComponent("otm.gui.quickmove_to.${name.lowercase()}") + } + } + + val input = menu.oneWayInput(handler = ::handle) + + fun clientInput() { + input.accept(null) + } + + private fun handle() { + mode.move(from, to, menu.player, dontTouchFilteredSlots) + } + + private data class SlotLists( + val empty: MutableList, + val withItems: MutableMap>, + val withFilters: MutableList, + ) + + companion object { + fun create(menu: MatteryMenu, from: Collection, to: Collection, dontTouchFilteredSlots: Boolean = true): Map { + return Mode.entries.associateWith { QuickMoveInput(menu, from, to, it, dontTouchFilteredSlots) } + } + + private fun computeSlotLists(slots: Collection, skipFilteredSlots: Boolean): SlotLists { + val emptySlots = ArrayList() + val filteredSlots = ArrayList() + val filledSlots = HashMap>() + + for (slot in slots) { + val underlyingSlot = slot.containerSlotOrNull() + + if (underlyingSlot is IFilteredContainerSlot && (underlyingSlot.filter.denyAll || !underlyingSlot.filter.allowAll && skipFilteredSlots)) + continue + + val key = slot.item.asKeyOrNull() + + if (key == null) { + if (underlyingSlot is IFilteredContainerSlot && underlyingSlot.filter.hasRules) { + filteredSlots.add(slot) + } else { + emptySlots.add(slot) + } + } else { + filledSlots.computeIfAbsent(key) { ArrayList() }.add(slot) + } + } + + return SlotLists(emptySlots, filledSlots, filteredSlots) + } + + fun moveItemStackTo( + player: Player, + source: Slot, + slots: Collection, + sort: Boolean = true + ): Boolean { + if (!source.mayPickup(player) || source.item.isEmpty || slots.isEmpty()) + return false + + val remainder = moveItemStackToSlots(source.item, slots, sort = sort) + + if (remainder.count == source.item.count) + return false + + val copy = source.item.copy() + + if (remainder.isEmpty) { + source.setByPlayer(ItemStack.EMPTY) + source.onTake(player, copy) + } else { + copy.count = source.item.count - remainder.count + source.item.count = remainder.count + source.onTake(player, copy) + } + + return true + } + + private val itemFilterSlotComparator = HasItemComparator.thenComparing(HasFilterComparator) + + fun > prioritySortSlotsInPlace(slots: T, filterItem: ItemStack? = null): T { + slots.removeIf { + val slot = it.containerSlotOrNull() + + it.isOverCapacity || + filterItem != null && !it.mayPlace(filterItem) || + slot is IFilteredContainerSlot && (slot.filter.denyAll || filterItem != null && !slot.filter.test(filterItem)) + } + + slots.sortWith(itemFilterSlotComparator) + return slots + } + + fun prioritySortSlots(slots: Collection, filterItem: ItemStack? = null): MutableList { + return prioritySortSlotsInPlace(ArrayList(slots), filterItem) + } + + fun moveItemStackToSlots(item: ItemStack, slots: Collection, simulate: Boolean = false, sort: Boolean = true): ItemStack { + if (item.isEmpty) + return ItemStack.EMPTY + else if (slots.isEmpty()) + return item.copy() + + val sortedSlots = if (sort) prioritySortSlots(slots, item) else slots + val copy = item.copy() + + for (slot in sortedSlots) { + val limit = slot.getMaxStackSize(copy) + + if (!slot.hasItem()) { + val newCount = copy.count.coerceAtMost(limit) + + if (!simulate) { + slot.setByPlayer(copy.copy().also { it.count = newCount }) + // slot.setChanged() + } + + copy.shrink(newCount) + + if (copy.isEmpty) + return ItemStack.EMPTY + } else if (limit > slot.item.count && ItemStack.isSameItemSameComponents(slot.item, copy)) { + val newCount = (slot.item.count + copy.count).coerceAtMost(limit) + val diff = newCount - slot.item.count + copy.count -= diff + + if (!simulate) { + slot.item.count += diff + slot.setChanged() + } + + if (copy.isEmpty) + return ItemStack.EMPTY + } + } + + return copy + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt index a39b17f62..64f4bc51e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt @@ -7,7 +7,6 @@ import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.Slot import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack -import net.neoforged.neoforge.capabilities.Capabilities import ru.dbotthepony.kommons.util.Delegate import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.value @@ -16,21 +15,27 @@ import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.UpgradeType import ru.dbotthepony.mc.otm.capability.energy -import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.container.IMatteryContainer +import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.IEnhancedContainer +import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.container.UpgradeContainer +import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull import ru.dbotthepony.mc.otm.core.collect.ConditionalEnumSet import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput -import ru.dbotthepony.mc.otm.runOnClient +import ru.dbotthepony.mc.otm.menu.input.ItemFilterInput +import ru.dbotthepony.mc.otm.network.StreamCodecs +import ru.dbotthepony.mc.otm.player.IPlayerInventorySlot import java.util.* import java.util.function.BooleanSupplier import java.util.function.DoubleSupplier import java.util.function.IntSupplier -import java.util.function.Predicate import java.util.function.Supplier import kotlin.reflect.KMutableProperty0 @@ -48,12 +53,20 @@ inline fun makeSlots(containers: List?, size: Int, initial return immutableList(size) { initializer(containers?.get(it) ?: SimpleContainer(1), 0) } } -open class MatterySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : Slot(container, index, x, y) { +open class MatteryMenuSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : Slot(container, index, x, y) { var ignoreSpectators = true + open fun setupNetworkControls(menu: MatteryMenu) { + val slot = containerSlotOrNull() + + if (slot is IFilteredContainerSlot && slot !is IPlayerInventorySlot) { + menu.mSynchronizer.add(Delegate.Of(slot::filter), StreamCodecs.ITEM_FILTER) + } + } + override fun setChanged() { - if (container is IMatteryContainer) { - (container as IMatteryContainer).setChanged(containerSlot) + if (container is IEnhancedContainer<*>) { + (container as IEnhancedContainer<*>).setChanged(containerSlot) } else { super.setChanged() } @@ -64,13 +77,13 @@ open class MatterySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) } open fun canTakeItemForPickAll(): Boolean { - return true + return (container.containerSlotOrNull(slotIndex) as? IFilteredContainerSlot)?.filter == null } override fun getMaxStackSize(): Int { val container = container - if (container is IMatteryContainer) { + if (container is IEnhancedContainer<*>) { return container.getMaxStackSize(slotIndex, ItemStack.EMPTY) } else { return super.getMaxStackSize() @@ -80,48 +93,39 @@ open class MatterySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) override fun getMaxStackSize(itemStack: ItemStack): Int { val container = container - if (container is IMatteryContainer) { + if (container is IEnhancedContainer<*>) { return container.getMaxStackSize(slotIndex, itemStack) } else { return super.getMaxStackSize(itemStack) } } -} -open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y), Predicate { - var hasSetFilter = false - private set - - var filter: Delegate? = null - set(value) { - hasSetFilter = true - field = value - } - - override fun canTakeItemForPickAll(): Boolean { - return filter?.get() == null - } - - override fun test(t: ItemStack): Boolean { - return filter?.get() == null || filter?.get() == t.item - } - - fun isSameFilter(other: Slot): Boolean { - if (other !is UserFilteredSlot) - return filter?.get() == null - - return ( - (other.filter == null && filter == null) || - (other.filter != null && filter != null && other.filter!!.get() == filter!!.get()) - ) + private fun isSameFilter(other: Slot): Boolean { + val sSelf = containerSlotOrNull() as? IFilteredContainerSlot + val sOther = other.containerSlotOrNull() as? IFilteredContainerSlot + return sSelf?.filter == sOther?.filter } override fun isSameInventory(other: Slot): Boolean { - return isSameFilter(other) && super.isSameInventory(other) + return super.isSameInventory(other) && isSameFilter(other) } } -open class OutputSlot(container: Container, index: Int, x: Int = 0, y: Int = 0, val onTake: (ItemStack) -> Unit = {}) : MatterySlot(container, index, x, y) { +open class UserFilteredMenuSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatteryMenuSlot(container, index, x, y) { + var filterInput: MatteryMenu.PlayerInput? = null + private set + + override fun setupNetworkControls(menu: MatteryMenu) { + super.setupNetworkControls(menu) + val slot = containerSlotOrNull() + + if (slot is IFilteredContainerSlot) { + filterInput = menu.PlayerInput(StreamCodecs.ITEM_FILTER, handler = { slot.filter = it }) + } + } +} + +open class OutputMenuSlot(container: Container, index: Int, x: Int = 0, y: Int = 0, val onTake: (ItemStack) -> Unit = {}) : MatteryMenuSlot(container, index, x, y) { override fun mayPlace(itemStack: ItemStack): Boolean { return false } @@ -137,112 +141,62 @@ open class OutputSlot(container: Container, index: Int, x: Int = 0, y: Int = 0, } } -open class BatterySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { +open class BatteryMenuSlot(container: Container, index: Int, x: Int = 0, y: Int = 0, val direction: FlowDirection = FlowDirection.BI_DIRECTIONAL) : UserFilteredMenuSlot(container, index, x, y) { override fun mayPlace(itemStack: ItemStack): Boolean { - return super.mayPlace(itemStack) && (itemStack.energy?.canExtract() ?: false) + if (!super.mayPlace(itemStack)) + return false + + val energy = itemStack.energy ?: return false + if (energy is IMatteryEnergyStorage) return direction.test(energy.energyFlow) + return direction.test(FlowDirection.of(energy.canReceive(), energy.canExtract())) } } -open class ChemicalFuelSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { +open class ChemicalFuelMenuSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatteryMenuSlot(container, index, x, y) { override fun mayPlace(itemStack: ItemStack): Boolean { return super.mayPlace(itemStack) && itemStack.getBurnTime(null) > 0 } } -open class ChargeSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { - override fun mayPlace(itemStack: ItemStack): Boolean { - return super.mayPlace(itemStack) && (itemStack.energy?.canReceive() ?: false) - } -} - -open class EnergyContainerInputSlot(container: Container, index: Int, x: Int = 0, y: Int = 0, val direction: FlowDirection = FlowDirection.BI_DIRECTIONAL) : MatterySlot(container, index, x, y) { - override fun mayPlace(itemStack: ItemStack): Boolean { - return super.mayPlace(itemStack) && (itemStack.getCapability(Capabilities.EnergyStorage.ITEM)?.let { direction.test(FlowDirection.of(it.canReceive(), it.canExtract())) } ?: false) - } -} - -open class MatterContainerInputSlot( +open class MatterContainerInputMenuSlot( container: Container, index: Int, x: Int = 0, y: Int = 0, val direction: FlowDirection = FlowDirection.BI_DIRECTIONAL -) : MatterySlot(container, index, x, y) { +) : UserFilteredMenuSlot(container, index, x, y) { override fun mayPlace(itemStack: ItemStack): Boolean { val handler = itemStack.getCapability(MatteryCapability.MATTER_ITEM) return handler != null && super.mayPlace(itemStack) && this.direction.test(handler.matterFlow) } } -open class PatternSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { +open class PatternMenuSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatteryMenuSlot(container, index, x, y) { override fun mayPlace(itemStack: ItemStack): Boolean { return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.PATTERN_ITEM) != null } } -open class DriveSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { +open class DriveMenuSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatteryMenuSlot(container, index, x, y) { override fun mayPlace(itemStack: ItemStack): Boolean { return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.CONDENSATION_DRIVE) != null } } -fun MatteryMenu.addFilterSlots(slots: Delegate): List> { - val result = ArrayList>(slots.value.size) - - for (i in 0 until slots.value.size) { - result.add(Delegate.Of( - mSynchronizer.computedItem { slots.value[i] }, - itemStackInput { slots.value = slots.value.set(i, it) } - )) - } - - return result +fun MatteryMenu.addFilterSlots(amount: Int, slots: Delegate?): ItemFilterInput { + return ItemFilterInput(this, amount, slots) } -fun MatteryMenu.addFilterSlots(amount: Int): List> { - val result = ArrayList>(amount) - - for (i in 0 until amount) { - result.add(Delegate.Of( - mSynchronizer.computedItem { ItemStack.EMPTY }, - itemStackInput { throw UnsupportedOperationException() } - )) - } - - return result +fun MatteryMenu.addFilterSlots(amount: Int): ItemFilterInput { + return addFilterSlots(amount, Delegate.Box(ItemFilterSet.EMPTY)) } -fun MatteryMenu.addFilterSlots(slots: Delegate?, amount: Int): List> { - if (slots != null && amount != slots.value.size) - throw IllegalStateException("Provided ItemFiler has different amount of slots than expected: ${slots.value.size} != $amount") - - if (slots == null) - return addFilterSlots(amount) - else - return addFilterSlots(slots) +fun MatteryMenu.addFilterSlots(amount: Int, slots: KMutableProperty0?): ItemFilterInput { + return addFilterSlots(amount, if (slots == null) null else Delegate.Of(slots)) } -fun MatteryMenu.addFilterSlots(slots: KMutableProperty0?, amount: Int): List> { - return addFilterSlots(if (slots == null) null else Delegate.Of(slots), amount) -} - -data class FilterControls(val slots: List>, val isWhitelist: BooleanInputWithFeedback, val matchComponents: BooleanInputWithFeedback, val matchTag: BooleanInputWithFeedback) - -fun MatteryMenu.addFilterControls(slots: Delegate?, amount: Int): FilterControls { - if (slots == null) { - return FilterControls(addFilterSlots(amount), BooleanInputWithFeedback(this), BooleanInputWithFeedback(this), BooleanInputWithFeedback(this)) - } else { - return FilterControls( - addFilterSlots(slots, amount), - BooleanInputWithFeedback.dispatch(this, slots, ItemFilter::isWhitelist, ItemFilter::isWhitelist), - BooleanInputWithFeedback.dispatch(this, slots, ItemFilter::matchComponents, ItemFilter::matchComponents), - BooleanInputWithFeedback.dispatch(this, slots, ItemFilter::matchTag, ItemFilter::matchTag), - ) - } -} - -fun MatteryMenu.addFilterControls(slots: KMutableProperty0?, amount: Int): FilterControls { - return addFilterControls(slots?.let { Delegate.Of(it) }, amount) +val Slot.isOverCapacity: Boolean get() { + return item.isNotEmpty && getMaxStackSize(item) <= item.count } /** @@ -250,7 +204,7 @@ fun MatteryMenu.addFilterControls(slots: KMutableProperty0?, amount: * in classloading exceptions. */ data class UpgradeSlots( - val slots: List, + val slots: List, val allowedTypes: Set, val openState: Delegate, val currentStats: IMatteryUpgrade, @@ -281,12 +235,12 @@ fun MatteryMenu.makeUpgradeSlots(count: Int, container: UpgradeContainer?): Upgr allowedTypes[value] = BooleanSupplier { b.get() } } - val syncContainer = container ?: SimpleContainer(count) + val syncContainer = container ?: EnhancedContainer.Simple(count) val isOpen = InstantBooleanInput(this) return UpgradeSlots( slots = immutableList(count) { - object : MatterySlot(syncContainer, it) { + object : MatteryMenuSlot(syncContainer, it) { init { mapQuickMoveToInventory(this) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/SortInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/SortInput.kt new file mode 100644 index 000000000..2f5617f78 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/SortInput.kt @@ -0,0 +1,20 @@ +package ru.dbotthepony.mc.otm.menu + +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.ints.IntCollection +import net.minecraft.network.RegistryFriendlyByteBuf +import net.minecraft.world.Container +import ru.dbotthepony.mc.otm.container.computeSortedIndices +import ru.dbotthepony.mc.otm.container.sortWithIndices +import ru.dbotthepony.mc.otm.network.MatteryStreamCodec +import ru.dbotthepony.mc.otm.network.StreamCodecs + +class SortInput(menu: MatteryMenu, val container: Container, val settings: IItemStackSortingSettings) { + val input = menu.PlayerInput(MatteryStreamCodec.Collection(StreamCodecs.VAR_INT, ::IntArrayList)) { + container.sortWithIndices(it) + } + + fun clientInput() { + input.accept(container.computeSortedIndices(settings.actualComparator)) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt index 2dc02ccf9..c718af908 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/CargoCrateMenu.kt @@ -1,12 +1,13 @@ package ru.dbotthepony.mc.otm.menu.decorative -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 ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.UserFilteredSlot +import ru.dbotthepony.mc.otm.menu.QuickMoveInput +import ru.dbotthepony.mc.otm.menu.SortInput +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.makeSlots import ru.dbotthepony.mc.otm.registry.game.MMenus @@ -15,11 +16,13 @@ class CargoCrateMenu( inventory: Inventory, tile: CargoCrateBlockEntity? = null ) : MatteryMenu(MMenus.CARGO_CRATE, containerId, inventory, tile) { - val actualContainer: Container = tile?.container ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY) - val storageSlots = makeSlots(actualContainer, ::UserFilteredSlot) + val actualContainer = tile?.container ?: SlottedContainer.filtered(CargoCrateBlockEntity.CAPACITY) + val storageSlots = makeSlots(actualContainer, ::UserFilteredMenuSlot) private val trackedPlayerOpen = !inventory.player.isSpectator - val sort = SortInput(actualContainer, playerSortSettings) + val sort = SortInput(this, actualContainer, playerSortSettings) + val quickMoveToStorage = QuickMoveInput.create(this, playerCombinedInventorySlots, storageSlots) + val quickMoveFromStorage = QuickMoveInput.create(this, storageSlots, playerInventorySlots, false) init { if (trackedPlayerOpen) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt index acebffafb..bce9927e0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/FluidTankMenu.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.menu.decorative -import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.item.ItemStack import net.neoforged.neoforge.capabilities.Capabilities @@ -8,9 +7,10 @@ import net.neoforged.neoforge.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.menu.OutputSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.menu.OutputMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.FluidConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput @@ -24,14 +24,16 @@ class FluidTankMenu(containerId: Int, inventory: Inventory, tile: FluidTankBlock val redstoneConfig = EnumInputWithFeedback(this) val fluidConfig = FluidConfigPlayerInput(this, tile?.fluidConfig) - val drainInput = object : MatterySlot(tile?.drainInput ?: SimpleContainer(1), 0) { + private val inputContainer = tile?.inputContainer ?: SlottedContainer.filtered(2) + + val drainInput = object : UserFilteredMenuSlot(inputContainer, 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return super.mayPlace(itemStack) && (itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.isNotEmpty ?: false) } } - val fillInput = object : MatterySlot(tile?.fillInput ?: SimpleContainer(1), 0) { + val fillInput = object : UserFilteredMenuSlot(inputContainer, 1) { override fun mayPlace(itemStack: ItemStack): Boolean { return super.mayPlace(itemStack) && (if (itemStack.count <= 1) itemStack @@ -46,7 +48,7 @@ class FluidTankMenu(containerId: Int, inventory: Inventory, tile: FluidTankBlock } } - val output = OutputSlot(tile?.output ?: SimpleContainer(1), 0) + val output = OutputMenuSlot(tile?.outputContainer ?: SlottedContainer.simple(1), 0) init { // сначала слот на заполнение из бака diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/GrillMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/GrillMenu.kt index c883ad281..57a021e42 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/GrillMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/GrillMenu.kt @@ -10,11 +10,12 @@ import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.item.crafting.SingleRecipeInput import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.block.entity.decorative.GrillBlockEntity -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.immutableList -import ru.dbotthepony.mc.otm.menu.ChemicalFuelSlot +import ru.dbotthepony.mc.otm.menu.ChemicalFuelMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import ru.dbotthepony.mc.otm.menu.makeSlots import ru.dbotthepony.mc.otm.menu.widget.IProgressGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.TakeExperienceWidget @@ -26,18 +27,10 @@ class GrillMenu( inventory: Inventory, tile: GrillBlockEntity? = null ) : MatteryMenu(MMenus.GRILL, containerId, inventory, tile) { - val fuelSlot = makeSlots(tile?.fuelSlot ?: object : MatteryContainer(1) { - override fun getMaxStackSize(): Int { - return 4 - } - }, ::ChemicalFuelSlot) + val fuelSlot = makeSlots(tile?.fuelSlot ?: SlottedContainer.simple(1, GrillBlockEntity.FUEL_SLOT_PROVIDER), ::ChemicalFuelMenuSlot) - val inputSlots = makeSlots(tile?.inputSlots ?: object : MatteryContainer(GrillBlockEntity.SLOTS) { - override fun getMaxStackSize(): Int { - return 1 - } - }) { c, i -> - object : MatterySlot(c, i) { + val inputSlots = makeSlots(tile?.inputSlots ?: SlottedContainer.simple(GrillBlockEntity.SLOTS, ContainerSlot.Simple(maxStackSize = 1))) { c, i -> + object : MatteryMenuSlot(c, i) { override fun onTake(p_150645_: Player, p_150646_: ItemStack) { super.onTake(p_150645_, p_150646_) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/MinecartCargoCrateMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/MinecartCargoCrateMenu.kt index 29a3b889b..613f24862 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/MinecartCargoCrateMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/MinecartCargoCrateMenu.kt @@ -7,7 +7,8 @@ import net.minecraft.world.entity.player.Player import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity import ru.dbotthepony.mc.otm.entity.MinecartCargoCrate import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.SortInput import ru.dbotthepony.mc.otm.menu.makeSlots import ru.dbotthepony.mc.otm.registry.game.MMenus @@ -17,11 +18,11 @@ class MinecartCargoCrateMenu( val cart: MinecartCargoCrate? = null ) : MatteryMenu(MMenus.CARGO_CRATE, containerId, inventory) { val actualContainer: Container = cart ?: SimpleContainer(CargoCrateBlockEntity.CAPACITY) - val storageSlots = makeSlots(actualContainer, ::MatterySlot) + val storageSlots = makeSlots(actualContainer, ::MatteryMenuSlot) private val trackedPlayerOpen = !inventory.player.isSpectator - val sort = SortInput(actualContainer, playerSortSettings) + val sort = SortInput(this, actualContainer, playerSortSettings) init { if (trackedPlayerOpen) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt index ba94a0e29..ec7eec0dc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/decorative/PainterMenu.kt @@ -16,14 +16,15 @@ import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity import ru.dbotthepony.mc.otm.player.matteryPlayer -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.addAll import ru.dbotthepony.mc.otm.core.collect.SupplierMap import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.collect.maybe import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.network.StreamCodecs @@ -45,8 +46,8 @@ class PainterMenu( val dyeStoredDirect = SupplierMap(dyeStored) val itemConfig = ItemConfigPlayerInput(this, tile?.config) - val inputContainer = MatteryContainer(::rescan, 1) - val outputContainer = MatteryContainer(1) + val inputContainer = SlottedContainer.simple(1, ::rescan) + val outputContainer = SlottedContainer.simple(1) private var lastRecipe: RecipeHolder? = null var selectedRecipe by mSynchronizer.add(ListenableDelegate.Box(null), StreamCodecs.RESOURCE_LOCATION.nullable()).also { it.addListener(Runnable { rescan() }) } @@ -56,7 +57,7 @@ class PainterMenu( selectedRecipe = it } - val inputSlot = object : MatterySlot(inputContainer, 0) { + val inputSlot = object : MatteryMenuSlot(inputContainer, 0) { override fun mayPlace(itemStack: ItemStack): Boolean { if (!itemStack.isEmpty && itemStack.`is`(ItemTags.DYEABLE)) { return super.mayPlace(itemStack) @@ -66,7 +67,7 @@ class PainterMenu( } } - val outputSlot = object : MatterySlot(outputContainer, 0) { + val outputSlot = object : MatteryMenuSlot(outputContainer, 0) { override fun tryRemove(p_150642_: Int, p_150643_: Int, p_150644_: Player): Optional { rescan() return super.tryRemove(p_150642_, p_150643_, p_150644_) @@ -79,7 +80,8 @@ class PainterMenu( if (isBulk.value) { val found = player.matteryPlayer.inventoryAndExopack .slotIterator() - .filter { !it.isForbiddenForAutomation && ItemStack.isSameItemSameComponents(it.item, inputSlot.item) } + //.filter { !it.isForbiddenForAutomation && ItemStack.isSameItemSameComponents(it.item, inputSlot.item) } + .filter { ItemStack.isSameItemSameComponents(it.item, inputSlot.item) } .maybe() if (found != null) { @@ -101,13 +103,13 @@ class PainterMenu( } } - val dyeSlot = object : MatterySlot(tile?.dyeInput ?: SimpleContainer(1), 0) { + val dyeSlot = object : MatteryMenuSlot(tile?.dyeInput ?: SlottedContainer.simple(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { - return super.mayPlace(itemStack) && (( - itemStack.getCapability(Capabilities.FluidHandler.ITEM)?.let { - dyeStoredDirect[null]!! < PainterBlockEntity.MAX_WATER_STORAGE && it.drain(FluidStack(Fluids.WATER, PainterBlockEntity.MAX_WATER_STORAGE - dyeStoredDirect[null]!!), IFluidHandler.FluidAction.SIMULATE).isNotEmpty - } ?: false - ) || (DyeColor.getColor(itemStack)?.let { dyeStoredDirect[it]!! + PainterBlockEntity.HUE_PER_ITEM <= PainterBlockEntity.MAX_STORAGE } ?: false)) + return super.mayPlace(itemStack) && ( + itemStack.getCapability(Capabilities.FluidHandler.ITEM) + ?.drain(FluidStack(Fluids.WATER, PainterBlockEntity.MAX_WATER_STORAGE - dyeStoredDirect[null]!!), IFluidHandler.FluidAction.SIMULATE)?.isNotEmpty + ?: false + || DyeColor.getColor(itemStack) != null) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemFilterInput.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemFilterInput.kt new file mode 100644 index 000000000..e944af6a6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/ItemFilterInput.kt @@ -0,0 +1,45 @@ +package ru.dbotthepony.mc.otm.menu.input + +import net.minecraft.world.entity.player.Player +import ru.dbotthepony.kommons.util.Delegate +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.network.StreamCodecs +import java.util.function.Predicate + +class ItemFilterInput(menu: MatteryMenu, maxSlots: Int, var filter: Delegate?, var allowRecursive: Boolean = false) { + val synchers = immutableList(maxSlots) { i -> + menu.mSynchronizer.computed({ filter?.get()?.get(i) ?: ItemFilter.EMPTY }, StreamCodecs.ITEM_FILTER) + } + + val inputs = immutableList(maxSlots) { i -> + menu.PlayerInput(StreamCodecs.ITEM_FILTER, handler = { + if (allowRecursive || it.depth <= 1) { + val filter = filter ?: return@PlayerInput + + if (it.hasRules) + filter.accept(filter.get().addOrReplace(i, it)) + else + filter.accept(filter.get().removeAt(i)) + } + }) + } + + val slots = immutableList(maxSlots) { i -> + Delegate.Of(synchers[i], inputs[i]) + } + + val isWhitelist = BooleanInputWithFeedback.dispatch( + menu, + Delegate.Of({ filter?.get() ?: ItemFilterSet.EMPTY }, { filter?.accept(it) }), + { it.isWhitelist }, + { it, v -> it.isWhitelist(v) } + ) + + fun filter(predicate: Predicate) { + inputs.forEach { it.filter(predicate) } + isWhitelist.input.filter(predicate) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt index b83cd4b6b..e21600457 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterBottlerMenu.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.menu.matter import com.google.common.collect.ImmutableList -import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.block.entity.matter.MatterBottlerBlockEntity @@ -9,11 +8,13 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.matter.canExtractMatter import ru.dbotthepony.mc.otm.capability.matter.canReceiveMatter import ru.dbotthepony.mc.otm.container.CombinedContainer +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.makeSlots import ru.dbotthepony.mc.otm.menu.makeUpgradeSlots import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget @@ -28,8 +29,8 @@ class MatterBottlerMenu( val progressWidget = ProgressGaugeWidget(this) val matterWidget = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter)) - val storageSlots: ImmutableList = makeSlots(CombinedContainer(tile?.bottling ?: SimpleContainer(3), tile?.unbottling ?: SimpleContainer(3))) { it, index -> - object : MatterySlot(it, index) { + val storageSlots: ImmutableList = makeSlots(CombinedContainer(tile?.bottling ?: SlottedContainer.filtered(3), tile?.unbottling ?: SlottedContainer.filtered(3))) { it, index -> + object : UserFilteredMenuSlot(it, index) { override fun mayPlace(itemStack: ItemStack): Boolean { val cap = itemStack.getCapability(MatteryCapability.MATTER_ITEM) ?: return false diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterCapacitorBankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterCapacitorBankMenu.kt index d117c9a07..1d76d930d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterCapacitorBankMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterCapacitorBankMenu.kt @@ -1,17 +1,18 @@ package ru.dbotthepony.mc.otm.menu.matter -import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.block.entity.matter.MatterCapacitorBankBlockEntity -import ru.dbotthepony.mc.otm.menu.MatterContainerInputSlot +import ru.dbotthepony.mc.otm.block.entity.tech.BatteryBankBlockEntity +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.menu.MatterContainerInputMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.registry.game.MMenus -class MatterCapacitorBankMenu @JvmOverloads constructor( +class MatterCapacitorBankMenu( p_38852_: Int, inventory: Inventory, tile: MatterCapacitorBankBlockEntity? = null @@ -20,7 +21,7 @@ class MatterCapacitorBankMenu @JvmOverloads constructor( val totalMatterGauge = LevelGaugeWidget(this) val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) - val storageSlots: List + val storageSlots: List init { if (tile != null) { @@ -31,10 +32,10 @@ class MatterCapacitorBankMenu @JvmOverloads constructor( }) } - val container = tile?.container ?: SimpleContainer(2 * 6) + val container = tile?.container ?: SlottedContainer.simple(BatteryBankBlockEntity.CAPACITY) - storageSlots = immutableList(2 * 6) { - addStorageSlot(MatterContainerInputSlot(container, it)) + storageSlots = immutableList(BatteryBankBlockEntity.CAPACITY) { + addStorageSlot(MatterContainerInputMenuSlot(container, it)) } addInventorySlots() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterDecomposerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterDecomposerMenu.kt index 07b5ef42f..5774b6f54 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterDecomposerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterDecomposerMenu.kt @@ -1,33 +1,32 @@ package ru.dbotthepony.mc.otm.menu.matter -import kotlin.jvm.JvmOverloads import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.matter.MatterDecomposerBlockEntity 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.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.matter.MatterManager -import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.OutputMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.makeUpgradeSlots import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.registry.game.MMenus -class MatterDecomposerMenu @JvmOverloads constructor( +class MatterDecomposerMenu( containerID: Int, inventory: Inventory, tile: MatterDecomposerBlockEntity? = null ) : MatteryPoweredMenu(MMenus.MATTER_DECOMPOSER, containerID, inventory, tile) { - val input = object : MatterySlot(tile?.inputContainer ?: SimpleContainer(1), 0) { + val input = object : UserFilteredMenuSlot(tile?.inputContainer ?: SlottedContainer.filtered(1), 0) { override fun mayPlace(itemStack: ItemStack) = MatterManager.canDecompose(itemStack) } - val outputMain: OutputSlot - val outputStacking: OutputSlot + val outputMain: OutputMenuSlot + val outputStacking: OutputMenuSlot val progressWidget = ProgressGaugeWidget(this, tile) val matterWidget = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter)) val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) @@ -36,11 +35,11 @@ class MatterDecomposerMenu @JvmOverloads constructor( val upgrades = makeUpgradeSlots(4, tile?.upgrades) init { - val container = tile?.outputContainer ?: SimpleContainer(2) + val container = tile?.outputContainer ?: SlottedContainer.simple(2) // Выход - outputMain = OutputSlot(container, 0) - outputStacking = OutputSlot(container, 1) + outputMain = OutputMenuSlot(container, 0) + outputStacking = OutputMenuSlot(container, 1) addStorageSlot(outputMain) addStorageSlot(outputStacking) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt index 3a3249333..aa99a86c9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterEntanglerMenu.kt @@ -1,22 +1,20 @@ 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 net.minecraft.world.item.crafting.CraftingInput 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.get import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer 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.MatteryMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.OutputMenuSlot +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.makeSlots @@ -39,12 +37,8 @@ class MatterEntanglerMenu( val progress = ProgressGaugeWidget(this, tile?.jobEventLoops?.get(0)) - val inputs: List = makeSlots(tile?.inputs ?: object : MatteryCraftingContainer(3, 3) { - override fun getMaxStackSize(): Int { - return 1 - } - }) { it, i -> - object : MatterySlot(it, i) { + val inputs: List = makeSlots(tile?.inputs ?: SlottedContainer.simple(3 * 3, AutomationFilters.ALLOW.limitedFilteredProvider)) { it, i -> + object : UserFilteredMenuSlot(it, i) { override fun mayPlace(itemStack: ItemStack): Boolean { val list = it.toList() list[i] = itemStack @@ -59,21 +53,15 @@ class MatterEntanglerMenu( } } - val outputs = makeSlots(tile?.output ?: SimpleContainer(1)) { a, b -> OutputSlot(a, b) { tile?.experience?.popExperience(player as ServerPlayer) } } + val outputs = makeSlots(tile?.output ?: SlottedContainer.simple(1, AutomationFilters.ONLY_OUT.unlimitedSimpleProvider)) { a, b -> OutputMenuSlot(a, b) { tile?.experience?.popExperience(player as ServerPlayer) } } val upgrades = makeUpgradeSlots(3, tile?.upgrades) val experience = TakeExperienceWidget(this, tile?.experience) - 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 val entangling = SlottedContainer.simple(2, AutomationFilters.ALLOW.limitedSimpleProvider) private fun rescan() { + if (tile == null) return + if (player is ServerPlayer && entangling[0].item is IQuantumLinked && entangling[1].item == entangling[0].item && entangling[0].isNotEmpty && entangling[1].isNotEmpty) { val result = (entangling[0].item as IQuantumLinked).merge(entangling[0], entangling[1]) entanglingC.container[0] = result @@ -82,15 +70,15 @@ class MatterEntanglerMenu( } } - private inner class EntanglingInputSlot(index: Int) : MatterySlot(entangling, index) { + private inner class EntanglingInputMenuSlot(index: Int) : MatteryMenuSlot(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(MatteryContainer(1), 0) { + val entanglingA: MatteryMenuSlot = EntanglingInputMenuSlot(0) + val entanglingB: MatteryMenuSlot = EntanglingInputMenuSlot(1) + val entanglingC: MatteryMenuSlot = object : MatteryMenuSlot(SlottedContainer.simple(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return false } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReconstructorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReconstructorMenu.kt index 6b2d5d83a..5fe2022cc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReconstructorMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReconstructorMenu.kt @@ -1,11 +1,11 @@ package ru.dbotthepony.mc.otm.menu.matter -import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.block.entity.matter.MatterReconstructorBlockEntity +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.makeUpgradeSlots @@ -20,7 +20,7 @@ class MatterReconstructorMenu( tile: MatterReconstructorBlockEntity? = null ) : MatteryPoweredMenu(MMenus.ITEM_REPAIER, containerId, inventory, tile) { val matterWidget = ProfiledLevelGaugeWidget(this, tile?.matter, LevelGaugeWidget(this, tile?.matter)) - val slot = object : MatterySlot(tile?.repairContainer ?: SimpleContainer(1), 0) { + val slot = object : UserFilteredMenuSlot(tile?.repairContainer ?: SlottedContainer.filtered(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return itemStack.isRepairable && itemStack.isDamaged && super.mayPlace(itemStack) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterRecyclerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterRecyclerMenu.kt index acc96bfc0..011aeb2fb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterRecyclerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterRecyclerMenu.kt @@ -4,9 +4,10 @@ import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.block.entity.matter.MatterRecyclerBlockEntity +import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.item.matter.MatterDustItem import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.makeUpgradeSlots @@ -20,7 +21,7 @@ class MatterRecyclerMenu @JvmOverloads constructor( inventory: Inventory, tile: MatterRecyclerBlockEntity? = null ) : MatteryPoweredMenu(MMenus.MATTER_RECYCLER, containerID, inventory, tile) { - val input = object : MatterySlot(tile?.container ?: SimpleContainer(1), 0) { + val input = object : MatteryMenuSlot(tile?.container ?: EnhancedContainer.Simple(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return itemStack.item is MatterDustItem && (itemStack.item as MatterDustItem).getMatterValue(itemStack) != null } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReplicatorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReplicatorMenu.kt index 0bbc18fec..a4824823a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReplicatorMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterReplicatorMenu.kt @@ -6,11 +6,12 @@ import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.matter.MatterReplicatorBlockEntity import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget -import net.minecraft.world.SimpleContainer import ru.dbotthepony.mc.otm.container.CombinedContainer +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.isNotEmpty -import ru.dbotthepony.mc.otm.menu.OutputSlot +import ru.dbotthepony.mc.otm.menu.OutputMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput @@ -26,7 +27,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 + val storageSlots: List val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) @@ -34,10 +35,10 @@ class MatterReplicatorMenu @JvmOverloads constructor( val upgrades = makeUpgradeSlots(3, tile?.upgrades) init { - val container = CombinedContainer(tile?.outputContainer ?: SimpleContainer(3), tile?.dustContainer ?: SimpleContainer(2)) + val container = CombinedContainer(tile?.outputContainer ?: EnhancedContainer.Simple(3), tile?.dustContainer ?: EnhancedContainer.Simple(2)) storageSlots = immutableList(5) { - addStorageSlot(OutputSlot(container, it, onTake = { + addStorageSlot(OutputMenuSlot(container, it, onTake = { if (inventory.player is ServerPlayer && it.isNotEmpty) TakeItemOutOfReplicatorTrigger.trigger(inventory.player as ServerPlayer, it) })) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterScannerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterScannerMenu.kt index 65a27c4f4..85157fb8d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterScannerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterScannerMenu.kt @@ -5,24 +5,24 @@ import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.matter.MatterScannerBlockEntity 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.container.EnhancedContainer import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.matter.MatterManager import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.makeUpgradeSlots import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.registry.game.MMenus -class MatterScannerMenu @JvmOverloads constructor( - p_38852_: Int, +class MatterScannerMenu( + containerID: Int, inventory: Inventory, tile: MatterScannerBlockEntity? = null -) : MatteryPoweredMenu(MMenus.MATTER_SCANNER, p_38852_, inventory, tile) { - val input: MatterySlot +) : MatteryPoweredMenu(MMenus.MATTER_SCANNER, containerID, inventory, tile) { + val input: MatteryMenuSlot val progress = ProgressGaugeWidget(this, tile) val patterns = LevelGaugeWidget(this) val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) @@ -31,9 +31,9 @@ class MatterScannerMenu @JvmOverloads constructor( val upgrades = makeUpgradeSlots(2, tile?.upgrades) init { - val container = tile?.container ?: SimpleContainer(1) + val container = tile?.container ?: EnhancedContainer.Simple(1) - input = object : MatterySlot(container, 0, 64, 38) { + input = object : MatteryMenuSlot(container, 0, 64, 38) { override fun mayPlace(itemStack: ItemStack) = MatterManager.canDecompose(itemStack) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/PatternStorageMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/PatternStorageMenu.kt index 2c21d118a..2ddc1a530 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/PatternStorageMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/PatternStorageMenu.kt @@ -4,9 +4,10 @@ import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.block.entity.matter.PatternStorageBlockEntity +import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.PatternSlot +import ru.dbotthepony.mc.otm.menu.PatternMenuSlot import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.widget.LevelGaugeWidget import ru.dbotthepony.mc.otm.registry.game.MMenus @@ -20,7 +21,7 @@ class PatternStorageMenu @JvmOverloads constructor( val storedGrid = LevelGaugeWidget(this) val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) - val storageSlots: List + val storageSlots: List init { if (tile != null) { @@ -31,10 +32,10 @@ class PatternStorageMenu @JvmOverloads constructor( }) } - val patterns = tile?.container ?: SimpleContainer(2 * 4) + val patterns = tile?.container ?: EnhancedContainer.Simple(2 * 4) - storageSlots = immutableList(2 * 4) { - addStorageSlot(PatternSlot(patterns, it)) + storageSlots = immutableList(patterns.containerSize) { + addStorageSlot(PatternMenuSlot(patterns, it)) } addInventorySlots() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveRackMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveRackMenu.kt index 9f9897b6f..e0e8c4b40 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveRackMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveRackMenu.kt @@ -4,7 +4,8 @@ import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.storage.DriveRackBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection -import ru.dbotthepony.mc.otm.menu.DriveSlot +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.menu.DriveMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback @@ -18,7 +19,7 @@ class DriveRackMenu @JvmOverloads constructor( inventory: Inventory, tile: DriveRackBlockEntity? = null ) : MatteryPoweredMenu(MMenus.DRIVE_RACK, containerId, inventory, tile) { - val storageSlots = makeSlots(tile?.container ?: SimpleContainer(4), ::DriveSlot) + val storageSlots = makeSlots(tile?.container ?: EnhancedContainer.Simple(4), ::DriveMenuSlot) val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) val insertPriority = IntInputWithFeedback(this, tile?.let { it::insertPriority }) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt index 994b3811f..1e10b89dc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/DriveViewerMenu.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.menu.storage -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 @@ -12,18 +11,22 @@ import ru.dbotthepony.mc.otm.block.entity.storage.DriveViewerBlockEntity import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage +import ru.dbotthepony.mc.otm.container.EnhancedContainer import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.ItemFilterInput import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget +import ru.dbotthepony.mc.otm.network.StreamCodecs import ru.dbotthepony.mc.otm.registry.game.MMenus import ru.dbotthepony.mc.otm.storage.ItemStorageStack import ru.dbotthepony.mc.otm.storage.StorageStack @@ -36,7 +39,7 @@ class DriveViewerMenu( ) : MatteryPoweredMenu(MMenus.DRIVE_VIEWER, containerID, inventory, tile), INetworkedItemViewProvider { override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null) - val driveSlot = object : MatterySlot(tile?.container ?: SimpleContainer(1), 0) { + val driveSlot = object : MatteryMenuSlot(tile?.container ?: EnhancedContainer.Simple(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return itemStack.getCapability(MatteryCapability.CONDENSATION_DRIVE) != null } @@ -74,26 +77,21 @@ class DriveViewerMenu( var drivePresent by mSynchronizer.boolean() - private fun getFilter(): ItemFilter? { + private fun getFilter(): ItemFilterSet { val stack = (tile as? DriveViewerBlockEntity)?.container?.getItem(0) - return (stack?.item as? PortableCondensationDriveItem)?.getFilterSettings(stack) + return (stack?.item as? PortableCondensationDriveItem)?.getFilterSettings(stack) ?: ItemFilterSet.EMPTY } - private fun setFilter(value: ItemFilter) { + private fun setFilter(value: ItemFilterSet) { val stack = (tile as? DriveViewerBlockEntity)?.container?.getItem(0) (stack?.item as? PortableCondensationDriveItem)?.setFilterSettings(stack, value) } - val driveFilterSlots = immutableList(PortableCondensationDriveItem.MAX_FILTERS) { i -> - Delegate.Of( - mSynchronizer.computedItem { getFilter()?.get(i) ?: ItemStack.EMPTY }, - itemStackInput { getFilter()?.set(i, it) }.filter { drivePresent } - ) - } + val driveFilter = ItemFilterInput(this, PortableCondensationDriveItem.MAX_FILTERS, Delegate.Of(::getFilter, ::setFilter)) - val isWhitelist = BooleanInputWithFeedback.dispatch(this, Delegate.Of({ getFilter() }, { setFilter(it!!) }), { it?.isWhitelist ?: false }, { it, v -> it?.isWhitelist(v) }).also { it.filter { drivePresent } } - val matchTag = BooleanInputWithFeedback.dispatch(this, Delegate.Of({ getFilter() }, { setFilter(it!!) }), { it?.matchTag ?: false }, { it, v -> it?.matchTag(v) }).also { it.filter { drivePresent } } - val matchComponents = BooleanInputWithFeedback.dispatch(this, Delegate.Of({ getFilter() }, { setFilter(it!!) }), { it?.matchComponents ?: false }, { it, v -> it?.matchComponents(v) }).also { it.filter { drivePresent } } + init { + driveFilter.filter { drivePresent } + } override fun broadcastChanges() { super.broadcastChanges() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/ItemMonitorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/ItemMonitorMenu.kt index 71c83bc06..2bba0944a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/ItemMonitorMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/ItemMonitorMenu.kt @@ -16,7 +16,8 @@ import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.collect.reduce import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.QuickMoveInput import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback @@ -27,7 +28,7 @@ import ru.dbotthepony.mc.otm.registry.game.MMenus import ru.dbotthepony.mc.otm.storage.* import java.util.* -private class ResultSlot(container: Container) : MatterySlot(container, 0) { +private class ResultMenuSlot(container: Container) : MatteryMenuSlot(container, 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return false } @@ -105,16 +106,16 @@ class ItemMonitorMenu( } } - val craftingResult: MatterySlot - val craftingSlots: List + val craftingResult: MatteryMenuSlot + val craftingSlots: List init { if (tile != null) { networkedItemView.component = tile.poweredView } - craftingResult = ResultSlot(tile?.craftingResultContainer ?: SimpleContainer(1)) - craftingSlots = makeSlots(tile?.craftingGrid ?: SimpleContainer(9), ::MatterySlot) + craftingResult = ResultMenuSlot(tile?.craftingResultContainer ?: SimpleContainer(1)) + craftingSlots = makeSlots(tile?.craftingGrid ?: SimpleContainer(9), ::MatteryMenuSlot) craftingSlots.forEach(this::addSlot) addSlot(craftingResult) @@ -137,9 +138,9 @@ class ItemMonitorMenu( if (settings.resultTarget == ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM) { remaining = view.insertStack(ItemStorageStack(itemStack), simulate).toItemStack() - remaining = moveItemStackToSlots(remaining, playerInventorySlots, simulate) + remaining = QuickMoveInput.moveItemStackToSlots(remaining, playerInventorySlots, simulate) } else { - remaining = moveItemStackToSlots(itemStack, playerInventorySlots, simulate) + remaining = QuickMoveInput.moveItemStackToSlots(itemStack, playerInventorySlots, simulate) remaining = view.insertStack(ItemStorageStack(remaining), simulate).toItemStack() } @@ -184,7 +185,7 @@ class ItemMonitorMenu( else -> {} } - remainder = moveItemStackToSlots(remainder, playerInventorySlots) + remainder = QuickMoveInput.moveItemStackToSlots(remainder, playerInventorySlots) slots[slotIndex].set(remainder) return if (remainder.count != item.count) item else ItemStack.EMPTY diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt index 27f750294..b091300c7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageBusMenu.kt @@ -4,7 +4,7 @@ import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.storage.StorageBusBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.addFilterControls +import ru.dbotthepony.mc.otm.menu.addFilterSlots import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.IntInputWithFeedback @@ -16,7 +16,7 @@ class StorageBusMenu( inventory: Inventory, tile: StorageBusBlockEntity? = null ) : MatteryPoweredMenu(MMenus.STORAGE_BUS, containerId, inventory, tile) { - val filter = addFilterControls(tile?.let { it::filter }, StorageBusBlockEntity.MAX_FILTERS) + val filter = addFilterSlots(StorageBusBlockEntity.MAX_FILTERS, tile?.let { it::filter }) val insertPriority = IntInputWithFeedback(this, tile?.let { it::insertPriority }) val extractPriority = IntInputWithFeedback(this, tile?.let { it::extractPriority }) val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt index 3f5d89d62..1337d6d83 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/storage/StorageImporterExporterMenu.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.menu.storage import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.storage.AbstractStorageImportExport import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.addFilterControls +import ru.dbotthepony.mc.otm.menu.addFilterSlots import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.registry.game.MMenus @@ -11,7 +11,7 @@ import ru.dbotthepony.mc.otm.registry.game.MMenus class StorageImporterExporterMenu( containerId: Int, inventory: Inventory, tile: AbstractStorageImportExport? = null ) : MatteryPoweredMenu(MMenus.STORAGE_IMPORTER_EXPORTER, containerId, inventory, tile) { - val filter = addFilterControls(tile?.let { it::filter }, AbstractStorageImportExport.MAX_FILTERS) + val filter = addFilterSlots(AbstractStorageImportExport.MAX_FILTERS, tile?.let { it::filter }) val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt index 3878047bd..19b042014 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/AndroidStationMenu.kt @@ -12,7 +12,7 @@ import ru.dbotthepony.mc.otm.player.MatteryPlayer import ru.dbotthepony.mc.otm.player.matteryPlayer import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget @@ -91,7 +91,7 @@ class AndroidStationMenu @JvmOverloads constructor( } } - private inner class AndroidSlot(container: Container, private val condition: (ItemStack) -> Boolean) : MatterySlot(container, 0) { + private inner class AndroidMenuSlot(container: Container, private val condition: (ItemStack) -> Boolean) : MatteryMenuSlot(container, 0) { override fun mayPickup(player: Player): Boolean { if (tile is AndroidStationBlockEntity) { return super.mayPickup(player) && tile.energy.batteryLevel >= MachinesConfig.AndroidStation.ENERGY_PER_OPERATION @@ -109,7 +109,7 @@ class AndroidStationMenu @JvmOverloads constructor( } } - val androidBattery: MatterySlot = AndroidSlot(container { it.androidEnergy::item }) { + val androidBattery: MatteryMenuSlot = AndroidMenuSlot(container { it.androidEnergy::item }) { it.getCapability(Capabilities.EnergyStorage.ITEM) != null } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt index aebd6ad6f..0c35515f1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/BatteryBankMenu.kt @@ -5,10 +5,11 @@ import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.tech.BatteryBankBlockEntity import net.minecraft.world.SimpleContainer import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.immutableList -import ru.dbotthepony.mc.otm.menu.BatterySlot +import ru.dbotthepony.mc.otm.menu.BatteryMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput @@ -21,7 +22,7 @@ class BatteryBankMenu( tile: BatteryBankBlockEntity? = null, ) : MatteryMenu(MMenus.BATTERY_BANK, p_38852_, inventory, tile) { val powerLevel = ProfiledLevelGaugeWidget(this, tile?.energyConfig?.energy) - val storageSlots: List + val storageSlots: List val redstone = EnumInputWithFeedback(this, RedstoneSetting::class.java) val energyConfig = EnergyConfigPlayerInput(this, allowPull = false, allowPush = true) val itemConfig = ItemConfigPlayerInput(this, allowPull = false, allowPush = false) @@ -33,10 +34,10 @@ class BatteryBankMenu( itemConfig.with(tile.itemConfig) } - val container: Container = tile?.container ?: SimpleContainer(BatteryBankBlockEntity.CAPACITY) + val container = tile?.container ?: SlottedContainer.filtered(BatteryBankBlockEntity.CAPACITY) storageSlots = immutableList(BatteryBankBlockEntity.CAPACITY) { - addStorageSlot(BatterySlot(container, it)) + addStorageSlot(BatteryMenuSlot(container, it)) } addInventorySlots() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt index 33b45a780..445d5433d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ChemicalGeneratorMenu.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.menu.tech -import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.item.ItemStack import net.neoforged.neoforge.capabilities.Capabilities @@ -8,8 +7,11 @@ import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.block.entity.tech.ChemicalGeneratorBlockEntity +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput @@ -30,19 +32,19 @@ class ChemicalGeneratorMenu @JvmOverloads constructor(id: Int, inv: Inventory, t } } - val fuelSlot = object : MatterySlot(tile?.fuelContainer ?: SimpleContainer(1), 0) { + val fuelSlot = object : UserFilteredMenuSlot(tile?.fuelContainer ?: SlottedContainer.filtered(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return itemStack.getBurnTime(null) > 0 } } - val residueSlot = object : MatterySlot(tile?.residueContainer ?: SimpleContainer(1), 0) { + val residueSlot = object : MatteryMenuSlot(tile?.residueContainer ?: EnhancedContainer.Simple(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return false } } - val batterySlot = object : MatterySlot(tile?.batteryContainer ?: SimpleContainer(1), 0) { + val batterySlot = object : UserFilteredMenuSlot(tile?.batteryContainer ?: SlottedContainer.filtered(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return itemStack.getCapability(Capabilities.EnergyStorage.ITEM)?.canReceive() ?: false } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt index 9eb8920b6..1bbc3b4e8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/CobblerMenu.kt @@ -1,29 +1,31 @@ package ru.dbotthepony.mc.otm.menu.tech -import net.minecraft.world.SimpleContainer 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.OutputSlot +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.menu.OutputMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput +import ru.dbotthepony.mc.otm.menu.makeSlots import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget import ru.dbotthepony.mc.otm.registry.game.MMenus -class CobblerMenu @JvmOverloads constructor( +class CobblerMenu( p_38852_: Int, 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(OutputSlot(c, it)) } } + val storageSlots = makeSlots(tile?.container ?: EnhancedContainer.Simple(CobblerBlockEntity.CONTAINER_SIZE), ::OutputMenuSlot) val redstone = EnumInputWithFeedback(this) val itemConfig = ItemConfigPlayerInput(this) val progress = ProgressGaugeWidget(this) init { + addStorageSlot(storageSlots) + if (tile != null) { progress.with(tile.jobEventLoops[0]) redstone.with(tile.redstoneControl::redstoneSetting) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt index 0f917784e..e95e7e6e2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyCounterMenu.kt @@ -12,19 +12,21 @@ import ru.dbotthepony.mc.otm.core.chart.DecimalHistoryChart import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.toDecimal import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.IntInputWithFeedback import ru.dbotthepony.mc.otm.registry.game.MMenus import java.math.BigDecimal +import java.util.function.Supplier class EnergyCounterMenu( p_38852_: Int, inventory: Inventory, tile: EnergyCounterBlockEntity? = null ) : MatteryMenu(MMenus.ENERGY_COUNTER, p_38852_, inventory, tile) { - var passed by mSynchronizer.decimal() - var lastTick by mSynchronizer.decimal() - var maxIO by mSynchronizer.decimal() + var passed by mSynchronizer.computedDecimal { tile?.passed ?: Decimal.ZERO } + var lastTick by mSynchronizer.computedDecimal { tile?.lastTick ?: Decimal.ZERO } + var maxIO by mSynchronizer.computedDecimal { tile?.ioLimit ?: Decimal.ZERO } val history5s = tile?.history5s ?: DecimalHistoryChart(1, 100) val history15s = tile?.history15s ?: DecimalHistoryChart(3, 100) @@ -34,6 +36,8 @@ class EnergyCounterMenu( val history6h = tile?.history6h ?: DecimalHistoryChart(720 * 6, 100) val history24h = tile?.history24h ?: DecimalHistoryChart(720 * 24, 100) + val pullEnergyFromInput = BooleanInputWithFeedback(this, tile?.let { it::pullEnergyFromInput }) + init { mSynchronizer.add(history5s) mSynchronizer.add(history15s) @@ -59,7 +63,7 @@ class EnergyCounterMenu( } } - var inputDirection: Direction by mSynchronizer.enum(Direction.UP) + val inputDirection: Direction by mSynchronizer.computedEnum { tile?.blockState?.getValue(EnergyCounterBlock.INPUT_DIRECTION) ?: Direction.UP} val maxIOInput = decimalInput { if (tile is EnergyCounterBlockEntity) { @@ -70,20 +74,4 @@ class EnergyCounterMenu( } } } - - override fun beforeBroadcast() { - super.beforeBroadcast() - - if (tile is EnergyCounterBlockEntity) { - passed = tile.passed - lastTick = tile.lastTick - inputDirection = tile.blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION) - - maxIO = tile.ioLimit?.toDecimal() ?: -Decimal.ONE - } - } - - companion object { - private val MINUS_ONE = -BigDecimal.ONE - } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyHatchMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyHatchMenu.kt index 94b8e70f8..e4f8c4c0e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyHatchMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyHatchMenu.kt @@ -1,12 +1,12 @@ package ru.dbotthepony.mc.otm.menu.tech -import net.minecraft.world.Container -import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.block.entity.tech.EnergyHatchBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection -import ru.dbotthepony.mc.otm.menu.EnergyContainerInputSlot +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.menu.BatteryMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.makeSlots @@ -19,10 +19,13 @@ class EnergyHatchMenu( inventory: Inventory, tile: EnergyHatchBlockEntity? = null ) : MatteryMenu(if (isInput) MMenus.ENERGY_INPUT_HATCH else MMenus.ENERGY_OUTPUT_HATCH, containerId, inventory, tile) { - val container: Container = tile?.container ?: SimpleContainer(EnergyHatchBlockEntity.CAPACITY) + val container = tile?.container ?: SlottedContainer.simple( + EnergyHatchBlockEntity.CAPACITY, + if (isInput) AutomationFilters.DISCHARGABLE.limitedFilteredProvider else AutomationFilters.CHARGEABLE.limitedFilteredProvider + ) val inputSlots = makeSlots(container) { a, b -> - EnergyContainerInputSlot(a, b, direction = FlowDirection.input(isInput)) + BatteryMenuSlot(a, b, direction = FlowDirection.input(isInput)) } val gauge = ProfiledLevelGaugeWidget(this, tile?.energy) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyServoMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyServoMenu.kt index 51809ffdb..61cdd9018 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyServoMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EnergyServoMenu.kt @@ -1,36 +1,25 @@ package ru.dbotthepony.mc.otm.menu.tech -import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory -import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.block.entity.tech.EnergyServoBlockEntity -import ru.dbotthepony.mc.otm.capability.energy +import ru.dbotthepony.mc.otm.capability.FlowDirection +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.menu.BatteryMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.registry.game.MMenus -class EnergyServoMenu @JvmOverloads constructor( +class EnergyServoMenu( p_38852_: Int, inventory: Inventory, tile: EnergyServoBlockEntity? = null ) : MatteryMenu(MMenus.ENERGY_SERVO, p_38852_, inventory, tile) { - val dischargeSlot = object : MatterySlot(tile?.discharge ?: SimpleContainer(1), 0) { - override fun mayPlace(itemStack: ItemStack): Boolean { - return super.mayPlace(itemStack) && (itemStack.energy?.canExtract() ?: false) - } - } - - val chargeSlot = object : MatterySlot(tile?.charge ?: SimpleContainer(1), 0) { - override fun mayPlace(itemStack: ItemStack): Boolean { - return super.mayPlace(itemStack) && (itemStack.energy?.canReceive() ?: false) - } - } - + val dischargeSlot = BatteryMenuSlot(tile?.discharge ?: SlottedContainer.filtered(1), 0, direction = FlowDirection.OUTPUT) + val chargeSlot = BatteryMenuSlot(tile?.charge ?: SlottedContainer.filtered(1), 0, direction = FlowDirection.INPUT) val equipment = makeEquipmentSlots(mapMoveToExternal = true) val powerGauge = ProfiledLevelGaugeWidget(this, tile?.energy) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt index 5abc2d3b5..1c362f84d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/EssenceStorageMenu.kt @@ -9,11 +9,14 @@ import net.minecraft.world.item.enchantment.EnchantmentHelper import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.util.getTotalXpRequiredForLevel import ru.dbotthepony.mc.otm.item.consumables.EssenceCapsuleItem import ru.dbotthepony.mc.otm.item.EssenceServoItem import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.FluidConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput @@ -31,19 +34,19 @@ class EssenceStorageMenu( val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig) val fluidConfig = FluidConfigPlayerInput(this, tile?.fluidConfig) - val capsuleSlot = object : MatterySlot(tile?.capsuleContainer ?: SimpleContainer(1), 0) { + val capsuleSlot = object : MatteryMenuSlot(tile?.capsuleContainer ?: EnhancedContainer.Simple(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return itemStack.item is EssenceCapsuleItem && super.mayPlace(itemStack) } } - val servoSlot = object : MatterySlot(tile?.servoContainer ?: SimpleContainer(1), 0) { + val servoSlot = object : MatteryMenuSlot(tile?.servoContainer ?: EnhancedContainer.Simple(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return itemStack.item == MItems.ESSENCE_SERVO && super.mayPlace(itemStack) } } - val mendingSlot = object : MatterySlot(tile?.mendingContainer ?: SimpleContainer(1), 0) { + val mendingSlot = object : UserFilteredMenuSlot(tile?.mendingContainer ?: SlottedContainer.filtered(1), 0) { override fun mayPlace(itemStack: ItemStack): Boolean { return itemStack.isDamaged && EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.REPAIR_WITH_XP) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ItemHatchMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ItemHatchMenu.kt index 620d9f509..9ebd095cb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ItemHatchMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/ItemHatchMenu.kt @@ -8,7 +8,8 @@ import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.block.entity.tech.ItemHatchBlockEntity import ru.dbotthepony.mc.otm.menu.MatteryMenu -import ru.dbotthepony.mc.otm.menu.UserFilteredSlot +import ru.dbotthepony.mc.otm.menu.SortInput +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.makeSlots import ru.dbotthepony.mc.otm.registry.game.MMenus @@ -20,15 +21,15 @@ class ItemHatchMenu( tile: ItemHatchBlockEntity? = null ) : MatteryMenu(if (isInput) MMenus.ITEM_INPUT_HATCH else MMenus.ITEM_OUTPUT_HATCH, containerId, inventory, tile) { val actualContainer: Container = tile?.container ?: SimpleContainer(ItemHatchBlockEntity.CAPACITY) - val storageSlots: ImmutableList = makeSlots(actualContainer) { a, b -> - object : UserFilteredSlot(a, b) { + val storageSlots: ImmutableList = makeSlots(actualContainer) { a, b -> + object : UserFilteredMenuSlot(a, b) { override fun mayPlace(itemStack: ItemStack): Boolean { return isInput && super.mayPlace(itemStack) } } } - val sort = SortInput(actualContainer, playerSortSettings) + val sort = SortInput(this, actualContainer, playerSortSettings) val redstone = EnumInputWithFeedback(this) init { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/MatterHatchMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/MatterHatchMenu.kt index 94ea8f34e..f61088744 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/MatterHatchMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/MatterHatchMenu.kt @@ -6,7 +6,9 @@ import net.minecraft.world.entity.player.Inventory import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.block.entity.tech.MatterHatchBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection -import ru.dbotthepony.mc.otm.menu.MatterContainerInputSlot +import ru.dbotthepony.mc.otm.container.slotted.AutomationFilters +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer +import ru.dbotthepony.mc.otm.menu.MatterContainerInputMenuSlot import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback import ru.dbotthepony.mc.otm.menu.makeSlots @@ -19,10 +21,10 @@ class MatterHatchMenu( inventory: Inventory, tile: MatterHatchBlockEntity? = null ) : MatteryMenu(if (isInput) MMenus.MATTER_INPUT_HATCH else MMenus.MATTER_OUTPUT_HATCH, containerId, inventory, tile) { - val container: Container = tile?.container ?: SimpleContainer(MatterHatchBlockEntity.CAPACITY) + val container: Container = tile?.container ?: SlottedContainer.simple(MatterHatchBlockEntity.CAPACITY, if (isInput) AutomationFilters.MATTER_PROVIDERS.limitedFilteredProvider else AutomationFilters.MATTER_CONSUMERS.limitedFilteredProvider) val inputSlots = makeSlots(container) { a, b -> - MatterContainerInputSlot(a, b, direction = FlowDirection.input(isInput)) + MatterContainerInputMenuSlot(a, b, direction = FlowDirection.input(isInput)) } val gauge = ProfiledLevelGaugeWidget(this, tile?.matter) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt index d1afe3c85..ed4eccf7c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt @@ -7,9 +7,11 @@ import net.minecraft.world.entity.player.Inventory import net.minecraft.world.inventory.MenuType import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity import ru.dbotthepony.mc.otm.compat.jei.PlatePressRecipeCategory +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.immutableList -import ru.dbotthepony.mc.otm.menu.OutputSlot -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.OutputMenuSlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput @@ -28,8 +30,8 @@ class PlatePressMenu( tile: PlatePressBlockEntity? = null, isTwin: Boolean ) : AbstractProcessingMachineMenu(type, containerID, inventory, tile) { - val inputSlots = makeSlots(tile?.inputContainer ?: SimpleContainer(if (isTwin) 2 else 1), ::MatterySlot) - val outputSlots = makeSlots(tile?.outputContainer ?: SimpleContainer(if (isTwin) 2 else 1)) { a, b -> OutputSlot(a, b) { tile?.experience?.popExperience(player as ServerPlayer) } } + val inputSlots = makeSlots(tile?.inputContainer ?: SlottedContainer.filtered(if (isTwin) 2 else 1), ::MatteryMenuSlot) + val outputSlots = makeSlots(tile?.outputContainer ?: EnhancedContainer.Simple(if (isTwin) 2 else 1)) { a, b -> OutputMenuSlot(a, b) { tile?.experience?.popExperience(player as ServerPlayer) } } val gauges = immutableList(if (isTwin) 2 else 1) { ProgressGaugeWidget(this, tile?.jobEventLoops?.get(it)) } override val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt index 8d709e278..024b854c1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PoweredFurnaceMenu.kt @@ -1,19 +1,24 @@ package ru.dbotthepony.mc.otm.menu.tech +import com.google.common.collect.ImmutableList import mezz.jei.api.constants.RecipeTypes import mezz.jei.api.recipe.RecipeType import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.inventory.MenuType +import net.minecraft.world.item.crafting.Recipe +import net.minecraft.world.item.crafting.SingleRecipeInput import ru.dbotthepony.mc.otm.block.entity.tech.AbstractPoweredFurnaceBlockEntity import ru.dbotthepony.mc.otm.block.entity.tech.PoweredBlastFurnaceBlockEntity import ru.dbotthepony.mc.otm.block.entity.tech.PoweredFurnaceBlockEntity import ru.dbotthepony.mc.otm.block.entity.tech.PoweredSmokerBlockEntity import ru.dbotthepony.mc.otm.compat.jei.MicrowaveRecipeCategory +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.core.immutableList -import ru.dbotthepony.mc.otm.menu.OutputSlot -import ru.dbotthepony.mc.otm.menu.MatterySlot +import ru.dbotthepony.mc.otm.menu.OutputMenuSlot +import ru.dbotthepony.mc.otm.menu.MatteryMenuSlot +import ru.dbotthepony.mc.otm.menu.UserFilteredMenuSlot import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput @@ -23,6 +28,7 @@ import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget import ru.dbotthepony.mc.otm.menu.widget.TakeExperienceWidget import ru.dbotthepony.mc.otm.registry.game.MMenus +import ru.dbotthepony.mc.otm.registry.game.MRecipes import java.util.function.Supplier class PoweredFurnaceMenu( @@ -31,8 +37,10 @@ class PoweredFurnaceMenu( inventory: Inventory, tile: AbstractPoweredFurnaceBlockEntity<*, *>? = null ) : AbstractProcessingMachineMenu(type, containerID, inventory, tile) { - val inputSlots = makeSlots(tile?.inputs ?: SimpleContainer(2), ::MatterySlot) - val outputSlots = makeSlots(tile?.outputs ?: SimpleContainer(2)) { c, s -> OutputSlot(c, s) { tile?.experience?.popExperience(player as ServerPlayer) } } + // we can't make these slots to reject non-smeltable items + // since mods may add obscure recipes/ingredients which never test true on client + val inputSlots = makeSlots(tile?.inputs ?: SlottedContainer.filtered(2), ::UserFilteredMenuSlot) + val outputSlots = makeSlots(tile?.outputs ?: EnhancedContainer.Simple(2)) { c, s -> OutputMenuSlot(c, s) { tile?.experience?.popExperience(player as ServerPlayer) } } val progressGauge = immutableList(2) { ProgressGaugeWidget(this, tile?.jobEventLoops?.get(it)) } override val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt index 29bf837a9..2c796bf21 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MatteryPlayerPackets.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.mc.otm.network import it.unimi.dsi.fastutil.bytes.ByteArrayList +import net.minecraft.core.BlockPos import net.minecraft.core.particles.ParticleTypes import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.RegistryFriendlyByteBuf @@ -9,27 +10,43 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack +import net.minecraft.world.level.BlockGetter +import net.minecraft.world.level.ChunkPos +import net.minecraft.world.level.block.entity.BlockEntity import net.neoforged.neoforge.network.handling.IPayloadContext import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.capability.IQuickStackContainer +import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.player.MatteryPlayer import ru.dbotthepony.mc.otm.player.matteryPlayer import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.set +import ru.dbotthepony.mc.otm.container.util.containerSlotOrNull import ru.dbotthepony.mc.otm.core.ResourceLocation +import ru.dbotthepony.mc.otm.core.math.Vector import ru.dbotthepony.mc.otm.core.math.component1 import ru.dbotthepony.mc.otm.core.math.component2 import ru.dbotthepony.mc.otm.core.math.component3 +import ru.dbotthepony.mc.otm.core.math.minus +import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.math.toRadians import ru.dbotthepony.mc.otm.core.otmRandom import ru.dbotthepony.mc.otm.core.position +import ru.dbotthepony.mc.otm.core.readEnum import ru.dbotthepony.mc.otm.core.readItem import ru.dbotthepony.mc.otm.core.writeItem +import ru.dbotthepony.mc.otm.entity.checkCanInteract import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu +import ru.dbotthepony.mc.otm.menu.QuickMoveInput import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashSet class MatteryPlayerDataPacket(val bytes: ByteArrayList, val isPublic: Boolean, val target: UUID? = null) : CustomPacketPayload { fun write(buff: FriendlyByteBuf) { @@ -414,6 +431,96 @@ object ResetExopackColorPacket : CustomPacketPayload { ) } +// as separate packet so it can be put into non-mattery menus +class QuickStackPacket( + val mode: QuickMoveInput.Mode, + val fromExopack: Boolean +) : CustomPacketPayload { + fun play(context: IPayloadContext) { + val player = context.player() as ServerPlayer + if (player.isSpectator) return + val radius = player.blockInteractionRange() + + val minChunkPos = ChunkPos(BlockPos.containing(player.position - Vector(radius, 0.0, radius))) + val maxChunkPos = ChunkPos(BlockPos.containing(player.position + Vector(radius, 0.0, radius))) + + val findCaps = ArrayList>() + + for (x in minChunkPos.x .. maxChunkPos.x) { + for (z in minChunkPos.z .. maxChunkPos.z) { + val chunk = player.serverLevel().chunkSource.getChunkNow(x, z) ?: continue + + for (blockEntity in chunk.blockEntities.values) { + if (player.checkCanInteract(blockEntity.blockPos)) { + val cap = player.level().getCapability(MatteryCapability.QUICK_STACK_CONTAINER, blockEntity.blockPos) ?: continue + findCaps.add(blockEntity to cap) + } + } + } + } + + if (findCaps.isEmpty()) return + + findCaps.sortBy { it.first.blockPos.distToCenterSqr(player.position) } + + val eyes = player.eyePosition + val ignorePositions = HashSet() + findCaps.forEach { (b) -> ignorePositions.add(b.blockPos) } + val level = player.serverLevel() + + // don't interact through walls + // but interact through chests + findCaps.removeIf { (b) -> + BlockGetter.traverseBlocks(eyes, Vector.atCenterOf(b.blockPos), null, { _, pos -> if (pos !in ignorePositions && !level.getBlockState(pos).isAir) true else null }, { false }) + } + + if (fromExopack) { + val prioritySlots = ArrayList() + val regularSlots = ArrayList>() + + for ((_, cap) in findCaps) { + val slots = cap.getSlotsFor(player) + + slots.forEach { + val slot = it.containerSlotOrNull() + + if (it.hasItem() || slot is IFilteredContainerSlot && !slot.filter.allowAll) + prioritySlots.add(it) + } + + regularSlots.add(slots) + } + + mode.move(player.matteryPlayer.exoPackMenu.playerCombinedInventorySlots, prioritySlots, player) + + regularSlots.forEach { + mode.move(player.matteryPlayer.exoPackMenu.playerCombinedInventorySlots, it, player) + } + } else { + for ((_, cap) in findCaps) + mode.move(cap.getSlotsFor(player), player.matteryPlayer.exoPackMenu.playerInventorySlots, player, false) + } + } + + override fun type(): CustomPacketPayload.Type { + return TYPE + } + + fun write(buff: FriendlyByteBuf) { + buff.writeEnum(mode) + buff.writeBoolean(fromExopack) + } + + companion object { + val TYPE = CustomPacketPayload.Type(ResourceLocation(OverdriveThatMatters.MOD_ID, "quick_stack")) + val CODEC: StreamCodec = StreamCodec.ofMember(QuickStackPacket::write, ::read) + + fun read(buff: FriendlyByteBuf): QuickStackPacket { + return QuickStackPacket(buff.readEnum(QuickMoveInput.Mode.entries), buff.readBoolean()) + } + } +} + class ExopackSmokePacket(val player: UUID) : CustomPacketPayload { fun write(buff: FriendlyByteBuf) { buff.writeUUID(player) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/NetworkPackets.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/NetworkPackets.kt index 07d715fc8..d23aad15e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/NetworkPackets.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/NetworkPackets.kt @@ -20,7 +20,7 @@ import ru.dbotthepony.mc.otm.menu.matter.ReplicationRequestPacket import ru.dbotthepony.mc.otm.menu.matter.TasksChangePacket internal fun registerNetworkPackets(event: RegisterPayloadHandlersEvent) { - val r = event.registrar("1.5.0") + val r = event.registrar("1.5.1") // world r @@ -54,6 +54,7 @@ internal fun registerNetworkPackets(event: RegisterPayloadHandlersEvent) { .playToServer(DisableExopackGlowPacket.TYPE, DisableExopackGlowPacket, DisableExopackGlowPacket::play) .playToServer(ResetExopackColorPacket.TYPE, ResetExopackColorPacket, ResetExopackColorPacket::play) .playToServer(SetExopackColorPacket.TYPE, SetExopackColorPacket.CODEC, SetExopackColorPacket::play) + .playToServer(QuickStackPacket.TYPE, QuickStackPacket.CODEC, QuickStackPacket::play) // otm player general r diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt index 1e0b59ff9..7479ced2e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/StreamCodecs.kt @@ -9,8 +9,9 @@ import net.minecraft.resources.ResourceLocation import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState import ru.dbotthepony.kommons.math.RGBAColor -import ru.dbotthepony.mc.otm.core.math.readDecimal -import ru.dbotthepony.mc.otm.core.math.writeDecimal +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.core.util.readDecimal +import ru.dbotthepony.mc.otm.core.util.writeDecimal import ru.dbotthepony.mc.otm.core.readBlockType import ru.dbotthepony.mc.otm.core.readItemType import ru.dbotthepony.mc.otm.core.writeBlockType @@ -43,6 +44,8 @@ object StreamCodecs { val ITEM_TYPE_NULLABLE = ITEM_TYPE.nullable() val DECIMAL = StreamCodec.of(FriendlyByteBuf::writeDecimal, FriendlyByteBuf::readDecimal).wrap() + val ITEM_FILTER = ByteBufCodecs.fromCodecWithRegistries(ItemFilter.CODEC).wrap() + fun composite( c0: StreamCodec, g0: (T) -> T0, c1: StreamCodec, g1: (T) -> T1, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableGroup.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableGroup.kt index 88813c500..9e7f0749c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableGroup.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/SynchableGroup.kt @@ -270,6 +270,10 @@ class SynchableGroup : Observer, ISynchable, Iterable { return add(ListenableDelegate.maskSmart(value, getter, setter), MatteryStreamCodec.Enum(value::class.java)) } + inline fun > computedEnum(value: Supplier): SynchableDelegate { + return computed(value, MatteryStreamCodec.Enum(E::class.java)) + } + fun decimal(value: Decimal = Decimal.ZERO, setter: DelegateSetter = DelegateSetter.passthrough(), getter: DelegateGetter = DelegateGetter.passthrough()): SynchableDelegate { return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.DECIMAL) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/player/ExopackContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/player/ExopackContainer.kt new file mode 100644 index 000000000..945f76272 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/player/ExopackContainer.kt @@ -0,0 +1,36 @@ +package ru.dbotthepony.mc.otm.player + +import net.minecraft.server.level.ServerPlayer +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.IContainerSlot +import ru.dbotthepony.mc.otm.container.IEnhancedContainer +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.triggers.MatteryInventoryChangeTrigger + +class ExopackContainer(size: Int, val player: MatteryPlayer) : EnhancedContainer(size) { + private inner class Slot(slot: Int) : IContainerSlot.Simple(slot, this@ExopackContainer), IPlayerInventorySlot { + override var shouldCharge: Boolean + get() = (PlayerInventoryWrapper.SLOTS + slot) in player.slotsChargeFlag + set(value) { if (value) player.slotsChargeFlag.add(PlayerInventoryWrapper.SLOTS + slot) else player.slotsChargeFlag.remove(PlayerInventoryWrapper.SLOTS + slot) } + + override var filter: ItemFilter + get() = player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] ?: ItemFilter.EMPTY + set(value) { if (value.allowAll) player.slotFilters.remove(PlayerInventoryWrapper.SLOTS + slot) else player.slotFilters[PlayerInventoryWrapper.SLOTS + slot] = value } + } + + override fun notifySlotChanged(slot: Int, old: ItemStack) { + super.notifySlotChanged(slot, old) + + if (player.ply is ServerPlayer) + MatteryInventoryChangeTrigger.trigger(player.ply, this, this[slot]) + } + + override fun containerSlot(slot: Int): IPlayerInventorySlot { + return Slot(slot) + } + + override val hasFilterableSlots: Boolean + get() = true +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/player/IPlayerInventorySlot.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/player/IPlayerInventorySlot.kt new file mode 100644 index 000000000..6719c2fb2 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/player/IPlayerInventorySlot.kt @@ -0,0 +1,7 @@ +package ru.dbotthepony.mc.otm.player + +import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot + +interface IPlayerInventorySlot : IFilteredContainerSlot { + var shouldCharge: Boolean +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryFoodData.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryFoodData.kt index 9f442d2df..cc547f8e6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryFoodData.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryFoodData.kt @@ -14,8 +14,7 @@ import ru.dbotthepony.mc.otm.config.IFoodRegenerationValues import ru.dbotthepony.mc.otm.config.PlayerConfig import ru.dbotthepony.mc.otm.core.damageType import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.getDecimal -import ru.dbotthepony.mc.otm.core.math.set +import ru.dbotthepony.mc.otm.core.nbt.getDecimal import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.registry.MDamageTypes import kotlin.math.max diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryPlayer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryPlayer.kt index ef4dbae2c..7f488e078 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryPlayer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/player/MatteryPlayer.kt @@ -2,6 +2,9 @@ package ru.dbotthepony.mc.otm.player import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import it.unimi.dsi.fastutil.ints.IntSet import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap @@ -12,6 +15,7 @@ import net.minecraft.core.HolderLookup import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag +import net.minecraft.nbt.NbtOps import net.minecraft.nbt.StringTag import net.minecraft.network.chat.Component import net.minecraft.network.protocol.common.custom.CustomPacketPayload @@ -75,11 +79,14 @@ import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.config.PlayerConfig import ru.dbotthepony.mc.otm.config.ExopackConfig import ru.dbotthepony.mc.otm.container.CombinedContainer -import ru.dbotthepony.mc.otm.container.DynamicallyProxiedContainer -import ru.dbotthepony.mc.otm.container.IContainer -import ru.dbotthepony.mc.otm.container.IMatteryContainer -import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.EnhancedContainer +import ru.dbotthepony.mc.otm.container.IContainerSlot +import ru.dbotthepony.mc.otm.container.IEnhancedContainer +import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.slotted.ContainerSlot +import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer import ru.dbotthepony.mc.otm.container.util.slotIterator import ru.dbotthepony.mc.otm.container.vanishCursedItems import ru.dbotthepony.mc.otm.core.* @@ -93,6 +100,7 @@ import ru.dbotthepony.mc.otm.core.nbt.getStringList import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.util.Savetables import ru.dbotthepony.mc.otm.core.util.TickList +import ru.dbotthepony.mc.otm.data.codec.minRange import ru.dbotthepony.mc.otm.menu.ExopackInventoryMenu import ru.dbotthepony.mc.otm.menu.IItemStackSortingSettings import ru.dbotthepony.mc.otm.network.* @@ -157,18 +165,6 @@ class MatteryPlayer(val ply: Player) { val level: Level get() = capability.ply.level() } - private inner class PlayerMatteryContainer(size: Int) : MatteryContainer(size) { - override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - if (ply is ServerPlayer) { - val item = new.copy() - - tickList.once { - MatteryInventoryChangeTrigger.trigger(ply, combinedInventory, item) - } - } - } - } - /** * For fields that need to be synchronized only to owning player * @@ -248,22 +244,20 @@ class MatteryPlayer(val ply: Player) { * If you want to properly extend Exopack suit capacity, add your value into this map */ val exopackSlotModifier = UUIDIntModifiersMap(observer = observer@{ - if (it < 0) { - exopackContainer = PlayerMatteryContainer(0) - } else { - exopackContainer = PlayerMatteryContainer(it) - } + exopackContainer = ExopackContainer(it.coerceAtLeast(0), this) }, backingMap = this.exopackSlotModifierMap.delegate) - val regularSlotFilters = immutableList(Inventory.INVENTORY_SIZE) { - syncher.add(null, StreamCodecs.ITEM_TYPE.nullable()) - } - val slotsChargeFlag = syncher.set( backing = ListenableSet(IntAVLTreeSet()), codec = StreamCodecs.VAR_INT, ).delegate + val slotFilters = syncher.map( + backing = ListenableMap(Int2ObjectOpenHashMap()), + keyCodec = StreamCodecs.VAR_INT, + valueCodec = StreamCodecs.ITEM_FILTER + ).delegate + private fun slotChargeToDefault() { // броня slotsChargeFlag.add(36) @@ -276,15 +270,12 @@ class MatteryPlayer(val ply: Player) { slotChargeToDefault() } - private var exopackContainerSynchedFilters: List = listOf() - /** * Exopack container, which actually store items inside Exopack */ - var exopackContainer: MatteryContainer = PlayerMatteryContainer(0) + var exopackContainer: ExopackContainer = ExopackContainer(0, this) private set(value) { _exoPackMenu = null - exopackContainerSynchedFilters.forEach { it.close() } @Suppress("SENSELESS_COMPARISON") // false positive - fields of player can easily be nulls, despite annotations saying otherwise if (ply.containerMenu != null && (ply !is ServerPlayer || ply.connection != null)) { @@ -299,7 +290,6 @@ class MatteryPlayer(val ply: Player) { } value.deserializeNBT(ply.level().registryAccess(), field.serializeNBT(ply.level().registryAccess())) - exopackContainerSynchedFilters = value.synchableFilters.map { syncher.add0(it) } field = value _combinedInventory = null @@ -307,36 +297,19 @@ class MatteryPlayer(val ply: Player) { _combinedInventory3 = null } - private var _combinedInventory: CombinedContainer? = null - private var _combinedInventory2: CombinedContainer? = null - private var _combinedInventory3: CombinedContainer? = null + private var _combinedInventory: CombinedContainer? = null + private var _combinedInventory2: CombinedContainer? = null + private var _combinedInventory3: CombinedContainer? = null - val wrappedInventory: IMatteryContainer = object : IMatteryContainer, IContainer by DynamicallyProxiedContainer(ply::getInventory) { - override fun getSlotFilter(slot: Int): Item? { - return regularSlotFilters.getOrNull(slot)?.get() - } + val wrappedInventory = PlayerInventoryWrapper(this) - override fun setSlotFilter(slot: Int, filter: Item?): Boolean { - regularSlotFilters.getOrNull(slot)?.accept(filter) - return true - } - - override fun setChanged(slot: Int) { - ply.inventory.setChanged() - } - - override fun clearSlotFilters() { - regularSlotFilters.forEach { it.accept(null) } - } - } - - val wrappedItemInventory: IMatteryContainer = object : IMatteryContainer by wrappedInventory { + val wrappedItemInventory: IEnhancedContainer = object : IEnhancedContainer by wrappedInventory { override fun getContainerSize(): Int { return 36 } } - val combinedInventory: CombinedContainer + val combinedInventory: CombinedContainer get() { if (_combinedInventory == null) _combinedInventory = CombinedContainer(wrappedInventory, exopackContainer) @@ -344,7 +317,7 @@ class MatteryPlayer(val ply: Player) { return _combinedInventory!! } - val inventoryAndExopack: CombinedContainer + val inventoryAndExopack: CombinedContainer get() { if (_combinedInventory2 == null) _combinedInventory2 = CombinedContainer(wrappedItemInventory, exopackContainer) @@ -352,10 +325,10 @@ class MatteryPlayer(val ply: Player) { return _combinedInventory2!! } - val inventoryAndExopackNoHotbar: CombinedContainer + val inventoryAndExopackNoHotbar: CombinedContainer get() { if (_combinedInventory3 == null) - _combinedInventory3 = CombinedContainer.Builder() + _combinedInventory3 = CombinedContainer.Builder() .add(wrappedItemInventory, 9 .. 35) .add(exopackContainer).build() @@ -534,8 +507,8 @@ class MatteryPlayer(val ply: Player) { } } - val input = MatteryContainer(Runnable { notify(IdleReason.ITEM) }, 1) - val output = MatteryContainer(Runnable { notify(IdleReason.ITEM) }, 1) + val input = EnhancedContainer.WithListener(1) { notify(IdleReason.ITEM) } + val output = EnhancedContainer.WithListener(1) { notify(IdleReason.ITEM) } init { savetables.stateful(::input, "exopack_smelter_input_$index") @@ -564,7 +537,7 @@ class MatteryPlayer(val ply: Player) { */ val exopackEnergy = ProfiledEnergyStorage(BatteryBackedEnergyStorage(ply, syncher, Decimal.ZERO, ExopackConfig.ENERGY_CAPACITY, false, onChange = { for (v in smelters) v.notify(MachineJobEventLoop.IdleReason.POWER) })) - val exopackChargeSlots = MatteryContainer(4) + val exopackChargeSlots = EnhancedContainer.Simple(4) var acceptExopackChargeFromWirelessCharger = true init { @@ -946,11 +919,8 @@ class MatteryPlayer(val ply: Player) { } } - tag["regularSlotFilters"] = ListTag().also { - for (filter in regularSlotFilters) { - it.add(StringTag.valueOf(filter.value?.registryName?.toString() ?: "")) - } - } + tag["slotFilters"] = filtersCodec.encodeStart(registry.createSerializationContext(NbtOps.INSTANCE), slotFilters.entries.map { it.key to it.value }) + .getOrThrow { IllegalStateException("Unable to serialize slot filters: $it") } tag.putIntArray("slotsChargeFlag", slotsChargeFlag.toIntArray()) return tag @@ -968,18 +938,16 @@ class MatteryPlayer(val ply: Player) { ExopackSlotsExpandedTrigger.trigger(ply, 0, exopackContainer.containerSize) } - for (filter in regularSlotFilters) { - filter.value = null - } - + slotFilters.clear() slotsChargeFlag.clear() - val regularSlotFilters = tag.getStringList("regularSlotFilters") - - for (i in 0 until regularSlotFilters.size.coerceAtMost(this.regularSlotFilters.size)) { - val path = regularSlotFilters[i].asString - if (path == "") continue - this.regularSlotFilters[i].value = BuiltInRegistries.ITEM.get(ResourceLocation.tryParse(path) ?: continue) + if ("slotFilters" in tag) { + filtersCodec.decode(registry.createSerializationContext(NbtOps.INSTANCE), tag["slotFilters"]) + .ifError { LOGGER.error("Unable to deserialize slot filters: ${it.message()}") } + .resultOrPartial() + .ifPresent { + it.first.forEach { (a, b) -> slotFilters[a] = b } + } } if ("slotsChargeFlag" in tag) { @@ -1332,6 +1300,15 @@ class MatteryPlayer(val ply: Player) { @Suppress("unused") companion object { + private val filtersCodec: Codec>> = Codec.list( + RecordCodecBuilder.create { + it.group( + Codec.INT.minRange(0).fieldOf("slot").forGetter { it.first }, + ItemFilter.CODEC.fieldOf("filter").forGetter { it.second } + ).apply(it, ::Pair) + } + ) + private val offhandSlotRange = IntSet.of(40) init { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/player/PlayerInventoryWrapper.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/player/PlayerInventoryWrapper.kt new file mode 100644 index 000000000..4371b50e5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/player/PlayerInventoryWrapper.kt @@ -0,0 +1,74 @@ +package ru.dbotthepony.mc.otm.player + +import net.minecraft.world.entity.player.Inventory +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.container.ISlottedContainer +import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.container.set + +class PlayerInventoryWrapper(val player: MatteryPlayer) : ISlottedContainer { + val inventory: Inventory + get() = player.ply.inventory + + private inner class Slot(val slot: Int) : IPlayerInventorySlot { + override fun setChanged() { + inventory.setChanged() + } + + override var filter: ItemFilter + get() = player.slotFilters[slot] ?: ItemFilter.EMPTY + set(value) { if (value.allowAll) player.slotFilters.remove(slot) else player.slotFilters[slot] = value } + override var shouldCharge: Boolean + get() = slot in player.slotsChargeFlag + set(value) { if (value) player.slotsChargeFlag.add(slot) else player.slotsChargeFlag.remove(slot) } + override var item: ItemStack + get() = inventory[slot] + set(value) { inventory[slot] = value } + override val maxStackSize: Int + get() = inventory.maxStackSize + + override fun maxStackSize(item: ItemStack): Int { + return inventory.getMaxStackSize(item) + } + + override fun remove(): ItemStack { + return inventory.removeItemNoUpdate(slot) + } + + override fun remove(count: Int): ItemStack { + return inventory.removeItem(slot, count) + } + } + + private val slots = Array(SLOTS) { Slot(it) } + + override val hasFilterableSlots: Boolean + get() = true + + override fun containerSlot(slot: Int): IPlayerInventorySlot { + return slots[slot] + } + + override fun clearContent() { + slots.forEach { it.remove() } + } + + override fun setChanged() { + inventory.setChanged() + } + + override fun getContainerSize(): Int { + return slots.size + } + + override fun stillValid(player: Player): Boolean { + return inventory.stillValid(player) + } + + companion object { + const val SLOTS = 41 + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt index b5999ed44..64e322653 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/recipe/MatterEntanglerRecipe.kt @@ -89,8 +89,12 @@ open class MatterEntanglerRecipe( return ingredients.ingredients() } + private val _isIncomplete by lazy(LazyThreadSafetyMode.PUBLICATION) { + result.isEmpty || ingredients.isIncomplete + } + override fun isIncomplete(): Boolean { - return result.isEmpty || ingredients.isIncomplete + return _isIncomplete } override fun isSpecial(): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBuiltInRegistries.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBuiltInRegistries.kt index e25efe5f1..c8e272e5a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBuiltInRegistries.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MBuiltInRegistries.kt @@ -60,6 +60,10 @@ object MBuiltInRegistries { defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "passthrough")) } + val ITEM_FILTER by Delegate(MRegistries.ITEM_FILTER) { + defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty")) + } + internal fun register(bus: IEventBus) { delegates.forEach { bus.addListener(it::build) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistries.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistries.kt index 16cc5ace4..6268477c3 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistries.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MRegistries.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.registry import net.minecraft.core.Registry import net.minecraft.resources.ResourceKey import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.core.ResourceLocation import ru.dbotthepony.mc.otm.data.world.BooleanProvider import ru.dbotthepony.mc.otm.data.world.DecimalProvider @@ -33,4 +34,5 @@ object MRegistries { val FEATURE = k>("feature") val CONFIGURED_FEATURE = k>("configured_feature") val PLACEMENT_MODIFIER = k>("placement_modifier") + val ITEM_FILTER = k>("item_filter") } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/game/MDataComponentTypes.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/game/MDataComponentTypes.kt index fbf77afc9..92fbcbfcc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/game/MDataComponentTypes.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/game/MDataComponentTypes.kt @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableList import com.mojang.serialization.Codec import net.minecraft.core.UUIDUtil import net.minecraft.core.component.DataComponentType -import net.minecraft.core.component.DataComponents import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.nbt.CompoundTag import net.minecraft.network.RegistryFriendlyByteBuf @@ -15,7 +14,7 @@ import net.neoforged.bus.api.IEventBus import net.neoforged.neoforge.fluids.SimpleFluidContent import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.matter.PatternState -import ru.dbotthepony.mc.otm.container.ItemFilter +import ru.dbotthepony.mc.otm.container.ItemFilterSet import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.item.tool.RedstoneInteractorItem @@ -80,7 +79,7 @@ object MDataComponentTypes { DataComponentType.builder>().persistent(Codec.list(PatternState.CODEC).xmap({ ImmutableList.copyOf(it) }, { it })).build() } - val ITEM_FILTER: DataComponentType by registry.register("item_filter") { DataComponentType.builder().persistent(ItemFilter.CODEC).build() } + val ITEM_FILTER: DataComponentType by registry.register("item_filter") { DataComponentType.builder().persistent(ItemFilterSet.CODEC).build() } val TICK_TIMER: DataComponentType by registry.register("tick_timer") { DataComponentType.builder().persistent(RedstoneInteractorItem.TickTimer.CODEC).build() } val EXPERIENCE: DataComponentType by registry.register("experience") { DataComponentType.builder().persistent(Codec.LONG).networkSynchronized(StreamCodecs.LONG).build() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/feature/BlackHolePlacer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/feature/BlackHolePlacer.kt index 102d3eccf..9a687d100 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/feature/BlackHolePlacer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/worldgen/feature/BlackHolePlacer.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.worldgen.feature -import com.mojang.serialization.Codec import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.world.level.levelgen.feature.Feature import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext @@ -8,7 +7,7 @@ import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfigur import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity import ru.dbotthepony.mc.otm.config.ServerConfig import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.nextDecimal +import ru.dbotthepony.mc.otm.core.nextDecimal import ru.dbotthepony.mc.otm.data.codec.DecimalCodec import ru.dbotthepony.mc.otm.registry.game.MBlocks diff --git a/src/main/resources/overdrive_that_matters.mixins.json b/src/main/resources/overdrive_that_matters.mixins.json index 9fafe0491..b1404830e 100644 --- a/src/main/resources/overdrive_that_matters.mixins.json +++ b/src/main/resources/overdrive_that_matters.mixins.json @@ -18,6 +18,7 @@ "DispenserBlockEntityMixin", "GuiGraphicsMixin", "BlockStateBaseMixin", + "ShulkerBoxBlockEntityMixin", "LevelMixin" ], "client": [