From 506017f0559632f763c95203a24a50d75cce95f9 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Thu, 10 Aug 2023 12:55:45 +0700 Subject: [PATCH] Bring storage system back to functional state Add ISubscriptable, which is implemented by networked fields and networked inputs Unify storage grid code Add itemstack and itemstoragestack sorters --- .../mc/otm/datagen/lang/English.kt | 23 +- .../mc/otm/datagen/lang/Russian.kt | 20 + .../mc/otm/OverdriveThatMatters.java | 1 - .../mc/otm/block/entity/MatteryBlockEntity.kt | 2 - .../block/entity/MatteryDeviceBlockEntity.kt | 1 - .../entity/decorative/FluidTankBlockEntity.kt | 1 - .../entity/matter/MatterBottlerBlockEntity.kt | 3 - .../entity/storage/DriveRackBlockEntity.kt | 15 +- .../entity/storage/DriveViewerBlockEntity.kt | 97 ++-- .../entity/storage/ItemMonitorBlockEntity.kt | 506 ++++++++---------- .../entity/storage/StorageBusBlockEntity.kt | 17 +- .../entity/tech/AndroidStationBlockEntity.kt | 5 - .../tech/ChemicalGeneratorBlockEntity.kt | 9 - .../entity/tech/EnergyServoBlockEntity.kt | 1 - .../entity/tech/EssenceStorageBlockEntity.kt | 1 - .../capability/drive/AbstractMatteryDrive.kt | 31 +- .../mc/otm/capability/drive/DrivePool.kt | 299 ++++------- .../client/screen/matter/MatterPanelScreen.kt | 29 +- .../mc/otm/client/screen/panels/FramePanel.kt | 20 +- .../screen/panels/NetworkedItemGridPanel.kt | 110 ++++ .../client/screen/panels/button/Buttons.kt | 36 +- .../screen/panels/slot/AbstractSlotPanel.kt | 2 +- .../client/screen/panels/util/GridPanel.kt | 22 +- .../screen/storage/DriveViewerScreen.kt | 129 ++--- .../screen/storage/ItemMonitorScreen.kt | 134 ++--- .../mc/otm/config/MachinesConfig.kt | 8 +- .../mc/otm/container/CombinedContainer.kt | 5 +- .../mc/otm/container/ContainerIterator.kt | 30 ++ .../kotlin/ru/dbotthepony/mc/otm/core/Ext.kt | 18 +- .../dbotthepony/mc/otm/core/GetterSetter.kt | 13 +- .../dbotthepony/mc/otm/core/ISubscripable.kt | 213 ++++++++ .../mc/otm/core/collect/StreamyIterator.kt | 6 +- .../mc/otm/core/nbt/CompoundTagExt.kt | 7 +- .../mc/otm/core/util/Formatting.kt | 5 +- .../mc/otm/core/util/ItemSorter.kt | 101 +++- .../dbotthepony/mc/otm/core/util/SiPrefix.kt | 1 + .../dbotthepony/mc/otm/core/util/TickList.kt | 16 + .../mc/otm/graph/storage/StorageNode.kt | 26 +- .../ru/dbotthepony/mc/otm/menu/MatteryMenu.kt | 15 +- .../ru/dbotthepony/mc/otm/menu/Slots.kt | 7 +- .../mc/otm/menu/data/NetworkedItemView.kt | 355 +++++------- .../input/AbstractPlayerInputWithFeedback.kt | 43 +- .../menu/input/BooleanInputWithFeedback.kt | 26 +- .../otm/menu/input/EnumInputWithFeedback.kt | 28 +- .../otm/menu/input/StringInputWithFeedback.kt | 2 +- .../mc/otm/menu/matter/MatterPanelMenu.kt | 4 +- .../mc/otm/menu/storage/DriveViewerMenu.kt | 55 +- .../mc/otm/menu/storage/ItemMonitorMenu.kt | 212 +++----- .../mc/otm/network/MenuNetworkChannel.kt | 7 +- .../network/synchronizer/FieldSynchronizer.kt | 230 ++++++-- .../mc/otm/network/synchronizer/Fields.kt | 18 +- .../otm/network/synchronizer/MutableFields.kt | 3 +- .../mc/otm/registry/MCreativeTabs.kt | 7 +- .../ru/dbotthepony/mc/otm/registry/MItems.kt | 4 +- .../ru/dbotthepony/mc/otm/storage/API.kt | 4 + .../mc/otm/storage/ItemStorageStack.kt | 5 + .../mc/otm/storage/StorageStack.kt | 14 +- .../mc/otm/storage/VirtualComponent.kt | 27 +- .../otm/storage/powered/PoweredComponent.kt | 12 + .../storage/powered/PoweredStorageAcceptor.kt | 12 + .../storage/powered/PoweredStorageProvider.kt | 55 +- .../powered/PoweredVirtualComponent.kt | 12 + 62 files changed, 1783 insertions(+), 1337 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NetworkedItemGridPanel.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/ISubscripable.kt 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 3cdaf9e97..46d65a59f 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 @@ -213,6 +213,27 @@ private fun misc(provider: MatteryLanguageProvider) { misc("suffix.zepto", "%s z%s") misc("suffix.yocto", "%s y%s") + misc("suffix_concise.none", "%s") + misc("suffix_concise.kilo", "%sk") + misc("suffix_concise.mega", "%sM") + misc("suffix_concise.giga", "%sG") + misc("suffix_concise.tera", "%sT") + misc("suffix_concise.peta", "%sP") + misc("suffix_concise.exa", "%sE") + misc("suffix_concise.zetta", "%sZ") + misc("suffix_concise.yotta", "%sY") + + misc("suffix_concise.deci", "%sd") + misc("suffix_concise.centi", "%sc") + misc("suffix_concise.milli", "%sm") + misc("suffix_concise.micro", "%sμ") + misc("suffix_concise.nano", "%sn") + misc("suffix_concise.pico", "%sp") + misc("suffix_concise.femto", "%sf") + misc("suffix_concise.atto", "%sa") + misc("suffix_concise.zepto", "%sz") + misc("suffix_concise.yocto", "%sy") + misc("suffix_raw.kilo", "k") misc("suffix_raw.mega", "M") misc("suffix_raw.giga", "G") @@ -776,7 +797,7 @@ private fun gui(provider: MatteryLanguageProvider) { gui("sorting.name", "Sort by name") gui("sorting.id", "Sort by ID") gui("sorting.modid", "Sort by Namespace (mod) ID") - gui("sorting.count", "Sort by amount") + gui("sorting.count", "Sort by quantity") gui("sorting.ascending", "Ascending") gui("sorting.descending", "Descending") gui("sorting.matter_value", "Matter value") 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 0db712185..22f20e08a 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 @@ -221,6 +221,26 @@ private fun misc(provider: MatteryLanguageProvider) { misc("suffix.zepto", "%s з%s") misc("suffix.yocto", "%s и%s") + misc("suffix_concise.none", "%s") + misc("suffix_concise.kilo", "%sк") + misc("suffix_concise.mega", "%sМ") + misc("suffix_concise.giga", "%sГ") + misc("suffix_concise.tera", "%sТ") + misc("suffix_concise.peta", "%sП") + misc("suffix_concise.exa", "%sЭ") + misc("suffix_concise.zetta", "%sЗ") + misc("suffix_concise.yotta", "%sИ") + misc("suffix_concise.deci", "%sд") + misc("suffix_concise.centi", "%sс") + misc("suffix_concise.milli", "%sм") + misc("suffix_concise.micro", "%s к") + misc("suffix_concise.nano", "%sн") + misc("suffix_concise.pico", "%sп") + misc("suffix_concise.femto", "%sф") + misc("suffix_concise.atto", "%sа") + misc("suffix_concise.zepto", "%sз") + misc("suffix_concise.yocto", "%sи") + misc("suffix_raw.kilo", "к") misc("suffix_raw.mega", "М") misc("suffix_raw.giga", "Г") diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index f729571bb..0dbfa6ced 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -142,7 +142,6 @@ public final class OverdriveThatMatters { } private void setup(final FMLCommonSetupEvent event) { - EVENT_BUS.addListener(EventPriority.LOWEST, DrivePool.INSTANCE::onServerPostTick); EVENT_BUS.addListener(EventPriority.HIGHEST, DrivePool.INSTANCE::serverStopEvent); EVENT_BUS.addListener(EventPriority.LOWEST, DrivePool.INSTANCE::serverStartEvent); EVENT_BUS.addListener(EventPriority.NORMAL, DrivePool.INSTANCE::onWorldSave); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt index 460a3b3b0..ec716f8fa 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt @@ -37,7 +37,6 @@ import net.minecraftforge.event.entity.player.PlayerEvent import net.minecraftforge.event.level.ChunkWatchEvent import net.minecraftforge.event.level.LevelEvent import net.minecraftforge.event.server.ServerStoppingEvent -import net.minecraftforge.items.IItemHandler import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.capability.MatteryCapability @@ -45,7 +44,6 @@ import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.isMekanismLoaded import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper import ru.dbotthepony.mc.otm.compat.mekanism.Mekanism2MatteryEnergyWrapper -import ru.dbotthepony.mc.otm.core.collect.SupplierList import ru.dbotthepony.mc.otm.core.collect.WeakHashSet import ru.dbotthepony.mc.otm.core.forValidRefs import ru.dbotthepony.mc.otm.core.get diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt index de41cc61a..9e9b368e1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.entity import com.google.common.collect.ImmutableSet -import mekanism.common.tile.interfaces.IRedstoneControl import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.core.BlockPos import net.minecraft.world.level.block.state.BlockState 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 ce5f0ac78..a5daaf14a 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 @@ -20,7 +20,6 @@ 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.core.ifPresentK import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.orNull import ru.dbotthepony.mc.otm.core.util.FluidStackValueCodec 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 d1d3a52db..5e622489b 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 @@ -9,17 +9,14 @@ import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.items.IItemHandler import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState -import ru.dbotthepony.mc.otm.capability.CombinedItemHandler import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.ProxiedItemHandler import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage -import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage import ru.dbotthepony.mc.otm.config.MachinesConfig 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 c46d9cd0a..c2711da3e 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 @@ -17,11 +17,12 @@ import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.menu.storage.DriveRackMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.storage.* +import ru.dbotthepony.mc.otm.storage.powered.PoweredComponent import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent -class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryPoweredBlockEntity(MBlockEntities.DRIVE_RACK, p_155229_, p_155230_) { +class DriveRackBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.DRIVE_RACK, blockPos, blockState) { val energy = WorkerEnergyStorage(this::setChangedLight, MachinesConfig.DRIVE_RACK) + val cell = StorageNode(energy) val container: MatteryContainer = object : MatteryContainer(this::setChanged, 4) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { @@ -31,21 +32,15 @@ class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : // but since we don't know generics of upvalue mattery drive, its storage type // is defined as out variant old.getCapability(MatteryCapability.DRIVE).ifPresent { - cell.computeIfAbsent(it.storageType as StorageStack.Type) { - PoweredVirtualComponent(VirtualComponent(it), ::energy) - }.remove(it as IMatteryDrive) + cell.removeStorageComponent(PoweredComponent(it, ::energy)) } new.getCapability(MatteryCapability.DRIVE).ifPresent { - cell.computeIfAbsent(it.storageType as StorageStack.Type) { - PoweredVirtualComponent(VirtualComponent(it), ::energy) - }.add(it as IMatteryDrive) + cell.addStorageComponent(PoweredComponent(it, ::energy)) } } }.also(::addDroppableContainer) - val cell = StorageNode(energy) - init { savetable(::energy, ENERGY_KEY) savetable(::container, INVENTORY_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 1dd82c294..fbe4d849c 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 @@ -1,5 +1,7 @@ package ru.dbotthepony.mc.otm.block.entity.storage +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import net.minecraft.core.BlockPos import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.player.Inventory @@ -8,71 +10,88 @@ import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState -import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity -import ru.dbotthepony.mc.otm.block.storage.DriveViewerBlock import ru.dbotthepony.mc.otm.block.entity.WorkerState +import ru.dbotthepony.mc.otm.block.storage.DriveViewerBlock 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.core.get +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu -import ru.dbotthepony.mc.otm.storage.StorageStack +import java.util.UUID -class DriveViewerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.DRIVE_VIEWER, p_155229_, p_155230_) { - override fun setChanged() { - super.setChanged() +class DriveViewerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.DRIVE_VIEWER, blockPos, blockState) { + val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyUpdated, MachinesConfig.DRIVE_VIEWER)) + val energyConfig = ConfigurableEnergy(energy) - val level = level - if (level is ServerLevel) - tickList.once { - var state = blockState - - if (container.getItem(0).getCapability(MatteryCapability.DRIVE).isPresent) { - state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING) - } else { - state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE) - } - - if (state !== blockState) { - level.setBlock(blockPos, state, Block.UPDATE_CLIENTS) - } - } - } - - val energy = WorkerEnergyStorage(this::setChangedLight, MachinesConfig.DRIVE_VIEWER) - - val container: MatteryContainer = object : MatteryContainer(this::setChanged, 1) { + val container: MatteryContainer = object : MatteryContainer(this::setChangedLight, 1) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { super.setChanged(slot, new, old) val level = level - if (level is ServerLevel) + if (level is ServerLevel) { tickList.once { - if (!isRemoved) { - var state = blockState + val isPresent = new.getCapability(MatteryCapability.DRIVE).isPresent + var state = this@DriveViewerBlockEntity.blockState.setValue(DriveViewerBlock.DRIVE_PRESENT, isPresent) - if (new.getCapability(MatteryCapability.DRIVE).isPresent) { - state = state.setValue(DriveViewerBlock.DRIVE_PRESENT, true) - } else { - state = state.setValue(DriveViewerBlock.DRIVE_PRESENT, false) - } + if (!isPresent) { + state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE) + } else if (energy.batteryLevel >= Decimal.TEN && !redstoneControl.isBlockedByRedstone) { + state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING) + } - if (state !== blockState) { - level.setBlock(blockPos, state, Block.UPDATE_CLIENTS) - } + if (state !== this@DriveViewerBlockEntity.blockState) { + level.setBlock(this@DriveViewerBlockEntity.blockPos, state, Block.UPDATE_CLIENTS) } } + } } }.also(::addDroppableContainer) + interface ISettings { + var sorting: ItemStorageStackSorter + var isAscending: Boolean + } + + inner class Settings : ISettings { + override var sorting: ItemStorageStackSorter = ItemStorageStackSorter.DEFAULT + set(value) { + field = value + setChangedLight() + } + + override var isAscending: Boolean = true + set(value) { + field = value + setChangedLight() + } + } + + val settings = Object2ObjectOpenHashMap() + + fun getSettingsFor(player: Player): ISettings { + return settings.computeIfAbsent(player.uuid, Object2ObjectFunction { Settings() }) + } + + private fun energyUpdated() { + val state = blockState.setValue(WorkerState.SEMI_WORKER_STATE, if (energy.batteryLevel >= Decimal.TEN && !redstoneControl.isBlockedByRedstone && blockState[DriveViewerBlock.DRIVE_PRESENT]) WorkerState.WORKING else WorkerState.IDLE) + + if (state != blockState) { + level?.setBlock(blockPos, state, Block.UPDATE_CLIENTS) + } else { + setChangedLight() + } + } + init { savetable(::energy, ENERGY_KEY) savetable(::container, INVENTORY_KEY) - - exposeEnergyGlobally(energy) } override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { 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 2f33be061..b95bb3dfe 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 @@ -1,10 +1,10 @@ package ru.dbotthepony.mc.otm.block.entity.storage +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import net.minecraft.core.BlockPos import net.minecraft.core.NonNullList import net.minecraft.nbt.CompoundTag -import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerPlayer import net.minecraft.world.Container @@ -19,19 +19,18 @@ import net.minecraft.world.level.Level import net.minecraft.world.level.block.state.BlockState import net.minecraftforge.common.ForgeHooks import net.minecraftforge.common.util.INBTSerializable -import net.minecraftforge.network.NetworkEvent -import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity 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.client.minecraft +import ru.dbotthepony.mc.otm.capability.matteryPlayer +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.MatteryContainer -import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.graph.storage.StorageNode import ru.dbotthepony.mc.otm.graph.storage.StorageGraph -import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.core.nbt.mapString @@ -41,122 +40,32 @@ import ru.dbotthepony.mc.otm.storage.* import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent import java.math.BigInteger import java.util.* -import java.util.function.Supplier import kotlin.collections.HashMap +import ru.dbotthepony.mc.otm.client.render.Widgets8 +import ru.dbotthepony.mc.otm.container.CombinedContainer +import ru.dbotthepony.mc.otm.container.addItem +import ru.dbotthepony.mc.otm.container.fullIterator +import ru.dbotthepony.mc.otm.container.iterator +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.util.ItemStorageStackSorter -class ItemMonitorPlayerSettings : INBTSerializable, MatteryPacket { - enum class IngredientPriority(val component: Component) { - // Refill everything from system - SYSTEM(TranslatableComponent("otm.gui.item_monitor.refill_source.system")), - - // Refill everything from player's inventory - INVENTORY(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory")), - - // Refill everything from system, if can't refill from player's inventory - SYSTEM_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.system_first")), - - // Refill everything from player's inventory, if can't refill from system - INVENTORY_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory_first")), - - // Do not refill (?) - DO_NOT(TranslatableComponent("otm.gui.item_monitor.refill_source.do_not")), - } - - enum class ResultTarget(val component: Component) { - // Everything goes into storage system - ALL_SYSTEM(TranslatableComponent("otm.gui.item_monitor.result_target.all_system")), - - // Result goes to player inventory - // Crafting remainder goes to storage system - MIXED(TranslatableComponent("otm.gui.item_monitor.result_target.mixed")), - - // Everything goes into player inventory - ALL_INVENTORY(TranslatableComponent("otm.gui.item_monitor.result_target.all_inventory")), - } - - enum class Amount(val component: Component) { - ONE(TranslatableComponent("otm.gui.item_monitor.amount.one")), - STACK(TranslatableComponent("otm.gui.item_monitor.amount.stack")), - FULL(TranslatableComponent("otm.gui.item_monitor.amount.full")) - } - - var ingredientPriority = IngredientPriority.SYSTEM - var resultTarget = ResultTarget.MIXED - var craftingAmount = Amount.STACK - - override fun serializeNBT(): CompoundTag { - return CompoundTag().also { - it[INGREDIENT_PRIORITY_KEY] = ingredientPriority.name - it[RESULT_TARGET_KEY] = resultTarget.name - it[QUICK_CRAFT_AMOUNT_KEY] = craftingAmount.name - } - } - - override fun deserializeNBT(nbt: CompoundTag) { - ingredientPriority = nbt.mapString(INGREDIENT_PRIORITY_KEY, IngredientPriority::valueOf, IngredientPriority.SYSTEM) - resultTarget = nbt.mapString(RESULT_TARGET_KEY, ResultTarget::valueOf, ResultTarget.MIXED) - craftingAmount = nbt.mapString(QUICK_CRAFT_AMOUNT_KEY, Amount::valueOf, Amount.STACK) - } - - fun read(buff: FriendlyByteBuf) { - ingredientPriority = buff.readEnum(IngredientPriority::class.java) - resultTarget = buff.readEnum(ResultTarget::class.java) - craftingAmount = buff.readEnum(Amount::class.java) - } - - fun read(other: ItemMonitorPlayerSettings) { - ingredientPriority = other.ingredientPriority - resultTarget = other.resultTarget - craftingAmount = other.craftingAmount - } - - override fun write(buff: FriendlyByteBuf) { - buff.writeEnum(ingredientPriority) - buff.writeEnum(resultTarget) - buff.writeEnum(craftingAmount) - } - - private fun playClient() { - val ply = minecraft.player ?: throw IllegalStateException("Player is missing") - val container = ply.containerMenu as? ItemMonitorMenu ?: return LOGGER.error("Received ItemMonitorPlayerSettings but container is missing or not of right type ({})!", ply.containerMenu) - - container.settings.read(this) - } - - // spectators are allowed because they change their own setting which is invisible to everyone else - private fun playServer(ply: ServerPlayer) { - val container = ply.containerMenu as? ItemMonitorMenu ?: return LOGGER.error("Received ItemMonitorPlayerSettings from {} but container is missing or not of right type ({})!", ply, ply.containerMenu) - container.settings.read(this) - (container.tile as ItemMonitorBlockEntity).setChangedLight() - } - - override fun play(context: Supplier) { - context.get().packetHandled = true - val ply = context.get().sender - - if (ply != null) { - playServer(ply) - } else { - playClient() - } - } - - companion object { - fun read(buff: FriendlyByteBuf): ItemMonitorPlayerSettings { - return ItemMonitorPlayerSettings().also { it.read(buff) } - } - - private val LOGGER = LogManager.getLogger() - const val INGREDIENT_PRIORITY_KEY = "ingredientPriority" - const val RESULT_TARGET_KEY = "resultTarget" - const val QUICK_CRAFT_AMOUNT_KEY = "quickCraftAmount" - } +interface IItemMonitorPlayerSettings { + var ingredientPriority: ItemMonitorPlayerSettings.IngredientPriority + var resultTarget: ItemMonitorPlayerSettings.ResultTarget + var craftingAmount: ItemMonitorPlayerSettings.Amount + var sorting: ItemStorageStackSorter + var ascendingSort: Boolean } -private fun takeOne(inventory: Inventory, item: ItemStack): Boolean { - for (i in 0 until inventory.containerSize) { - if (!inventory[i].isEmpty && ItemStack.isSameItemSameTags(inventory[i], item)) { - inventory.removeItem(i, 1) +private fun takeOne(inventory: CombinedContainer?, item: ItemStack): Boolean { + val iterator = inventory?.optimizedIterator() ?: return false + + for (stack in iterator) { + if (stack.equals(item, false)) { + stack.shrink(1) + inventory.setChanged() return true } } @@ -164,21 +73,110 @@ private fun takeOne(inventory: Inventory, item: ItemStack): Boolean { return false } -private fun takeOne(id: UUID?, view: IStorageProvider): Boolean { - val extracted = view.extractStack(id ?: return false, BigInteger.ONE, false) +private fun takeOne(id: UUID?, view: IStorageProvider?): Boolean { + return view?.extractStack(id ?: return false, BigInteger.ONE, false)?.isNotEmpty == true +} - if (!extracted.isEmpty) { - return true +class ItemMonitorPlayerSettings : INBTSerializable, IItemMonitorPlayerSettings { + interface Setting { + val component: Component + val icon: IGUIRenderable + val winding: UVWindingOrder } - return false + 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 { + 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 { + 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 { + 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 { + 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 { + return false + } + }; + + override val icon: IGUIRenderable by icon + + 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 { + // Everything goes into storage system + ALL_SYSTEM(TranslatableComponent("otm.gui.item_monitor.result_target.all_system"), lazy { Widgets8.ARROW_PAINTED_UP }), + + // Result goes to player inventory + // Crafting remainder goes to storage system + MIXED(TranslatableComponent("otm.gui.item_monitor.result_target.mixed"), lazy { Widgets8.ARROW_SIDEWAYS }), + + // Everything goes into player inventory + ALL_INVENTORY(TranslatableComponent("otm.gui.item_monitor.result_target.all_inventory"), lazy { Widgets8.ARROW_PAINTED_UP }, UVWindingOrder.FLIP); + + override val icon: IGUIRenderable by icon + } + + enum class Amount(override val component: Component, icon: Lazy, override val winding: UVWindingOrder = UVWindingOrder.NORMAL) : Setting { + ONE(TranslatableComponent("otm.gui.item_monitor.amount.one"), lazy { Widgets8.ONE }), + STACK(TranslatableComponent("otm.gui.item_monitor.amount.stack"), lazy { Widgets8.S }), + FULL(TranslatableComponent("otm.gui.item_monitor.amount.full"), lazy { Widgets8.F }); + + override val icon: IGUIRenderable by icon + } + + override var ingredientPriority = IngredientPriority.SYSTEM + override var resultTarget = ResultTarget.MIXED + override var craftingAmount = Amount.STACK + override var sorting: ItemStorageStackSorter = ItemStorageStackSorter.DEFAULT + override var ascendingSort: Boolean = false + + override fun serializeNBT(): CompoundTag { + return CompoundTag().also { + it["ingredientPriority"] = ingredientPriority.name + it["resultTarget"] = resultTarget.name + it["quickCraftAmount"] = craftingAmount.name + it["sorting"] = sorting.name + it["ascendingSort"] = ascendingSort + } + } + + override fun deserializeNBT(nbt: CompoundTag) { + ingredientPriority = nbt.mapString("ingredientPriority", IngredientPriority::valueOf, IngredientPriority.SYSTEM) + resultTarget = nbt.mapString("resultTarget", ResultTarget::valueOf, ResultTarget.MIXED) + craftingAmount = nbt.mapString("quickCraftAmount", Amount::valueOf, Amount.STACK) + sorting = nbt.mapString("sorting", ItemStorageStackSorter::valueOf, ItemStorageStackSorter.DEFAULT) + ascendingSort = nbt.getBoolean("ascendingSort") + } } class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.ITEM_MONITOR, blockPos, blockState), IStorageEventConsumer { - val energy = WorkerEnergyStorage(this::setChangedLight, MachinesConfig.ITEM_MONITOR) + val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::setChangedLight, MachinesConfig.ITEM_MONITOR)) + val energyConfig = ConfigurableEnergy(energy) init { - exposeEnergyGlobally(energy) savetable(::energy, ENERGY_KEY) } @@ -203,79 +201,74 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte } private val settings = HashMap() - private val craftingAmount = Object2ObjectArrayMap() + private val craftingAmount = Object2IntOpenHashMap() private val lastCraftingRecipe = Object2ObjectArrayMap() + private var inProcessOfCraft = false + private val craftingGridTuples = arrayOfNulls(3 * 3) - fun howMuchPlayerCrafted(ply: Player): Int = craftingAmount[ply] ?: 0 + 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 : MatteryContainer(::setChangedLight, 3 * 3) { + val craftingGrid = object : MatteryContainer(3 * 3) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { - super.setChanged(slot, new, old) - craftingGridDummy[slot] = new + setChangedLight() + craftingGridVanilla[slot] = new - if (!inProcessOfCraft) - scanCraftingGrid() + if (!inProcessOfCraft) { + scanCraftingGrid(false) + } } }.also(::addDroppableContainer) - private var inProcessOfCraft = false + private val craftingGridVanilla = TransientCraftingContainer(object : AbstractContainerMenu(null, Int.MIN_VALUE) { + override fun stillValid(p_38874_: Player) = true + override fun quickMoveStack(p_38941_: Player, p_38942_: Int) = ItemStack.EMPTY + }, 3, 3) - private fun scanCraftingGrid() { - val server = level?.server ?: return - val oldRecipe = craftingRecipe + private fun scanCraftingGrid(justCheckForRecipeChange: Boolean): Boolean { + val level = level ?: return false + val server = level.server ?: return false - if (oldRecipe != null && oldRecipe.matches(craftingGridDummy, level!!)) { - return - } + var craftingRecipe = craftingRecipe + if (craftingRecipe != null && craftingRecipe.matches(craftingGridVanilla, level)) return true + if (justCheckForRecipeChange) return false - craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGridDummy, level!!).orElse(null) - val craftingRecipe = craftingRecipe + craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGridVanilla, level).orElse(null) + Arrays.fill(craftingGridTuples, null) + val poweredView = poweredView - if (oldRecipe != craftingRecipe) { - Arrays.fill(craftingGridTuples, null) + if (craftingRecipe != null && poweredView != null) { + for (i in craftingGridTuples.indices) { + val item = craftingGrid[i] - val poweredView = poweredView - - if (craftingRecipe != null && poweredView != null) { - for (i in craftingGridTuples.indices) { - val item = craftingGrid[i] - - if (!item.isEmpty) { - craftingGridTuples[i] = poweredView[ItemStorageStack.unsafe(item)] - } + if (!item.isEmpty) { + craftingGridTuples[i] = poweredView[ItemStorageStack.unsafe(item)] } } } + + this.craftingRecipe = craftingRecipe + return false } - private val craftingGridTuples = arrayOfNulls(3 * 3) - - private val craftingMenu = object : AbstractContainerMenu(null, Int.MIN_VALUE) { - override fun stillValid(p_38874_: Player): Boolean { - return true - } - - override fun quickMoveStack(p_38941_: Player, p_38942_: Int): ItemStack { - return ItemStack.EMPTY - } - } - - private val craftingGridDummy = TransientCraftingContainer(craftingMenu, 3, 3) - override val storageType: StorageStack.Type get() = StorageStack.ITEMS override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider) { - // no op - } - - override fun onStackChanged(stack: ItemStorageStack, id: UUID) { - // no op + if (craftingRecipe != null) { + for (i in craftingGridTuples.indices) { + val item = craftingGrid[i] + + if (!item.isEmpty && stack.equalsWithoutCount(ItemStorageStack.unsafe(item))) { + craftingGridTuples[i] = id + } + } + } } + override fun onStackChanged(stack: ItemStorageStack, id: UUID) {} override fun onStackRemoved(id: UUID) { for (i in craftingGridTuples.indices) { if (craftingGridTuples[i] == id) { @@ -300,137 +293,112 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte override fun getContainerSize() = 1 override fun isEmpty() = craftingRecipe == null - override fun stillValid(p_18946_: Player) = true + override fun stillValid(player: Player) = true - override fun getItem(p_18941_: Int): ItemStack { - require(p_18941_ == 0) { "Invalid slot ID: $p_18941_" } + override fun getItem(slot: Int): ItemStack { + require(slot == 0) { "Invalid slot: $slot" } return craftingRecipe?.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY)?.copy() ?: ItemStack.EMPTY } override fun removeItem(index: Int, amount: Int): ItemStack { require(index == 0) { "Invalid index $index" } - val craftingRecipe = craftingRecipe - val craftingPlayer = craftingPlayer + val level = level ?: return ItemStack.EMPTY + val craftingRecipe = craftingRecipe ?: return ItemStack.EMPTY + val craftingPlayer = craftingPlayer ?: return ItemStack.EMPTY - if (craftingRecipe == null || craftingPlayer == null || craftingRecipe.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY).count != amount) { - return ItemStack.EMPTY + try { + ForgeHooks.setCraftingPlayer(craftingPlayer) + + if (craftingRecipe.getResultItem(level.registryAccess()).count != amount) { + return ItemStack.EMPTY + } + } finally { + ForgeHooks.setCraftingPlayer(null) } - var crafts = craftingAmount[craftingPlayer] ?: 0 - crafts++ - craftingAmount[craftingPlayer] = crafts + craftingAmount[craftingPlayer] = craftingAmount.getInt(craftingPlayer) + 1 lastCraftingRecipe[craftingPlayer] = craftingRecipe + tickList.namedTimer(craftingPlayer, 4) { + craftingAmount.removeInt(craftingPlayer) + lastCraftingRecipe.remove(craftingPlayer) + } + val settings = getSettings(craftingPlayer) inProcessOfCraft = true try { ForgeHooks.setCraftingPlayer(craftingPlayer) - val remainingItems: NonNullList = craftingRecipe.getRemainingItems(craftingGridDummy) - ForgeHooks.setCraftingPlayer(null) + val residue: NonNullList + val result: ItemStack - check(remainingItems.size == craftingGrid.containerSize) { "${remainingItems.size} != ${craftingGrid.containerSize} !!!" } + try { + residue = craftingRecipe.getRemainingItems(craftingGridVanilla) + result = craftingRecipe.getResultItem(level.registryAccess()) + } finally { + ForgeHooks.setCraftingPlayer(null) + } + check(residue.size == craftingGrid.containerSize) { "Container and residue list sizes mismatch: ${residue.size} != ${craftingGrid.containerSize}" } + + val combinedInventory = craftingPlayer.matteryPlayer?.combinedInventory + val copy = craftingGrid.fullIterator().map { it.copy() }.toList() + + // удаляем по одному предмету из сетки крафта for (slot in 0 until craftingGrid.containerSize) { - val oldItem = craftingGrid[slot].let { if (!it.isEmpty || it.count < 2) it.copy() else it } + craftingGrid.removeItem(slot, 1) - if (!craftingGrid[slot].isEmpty) { - craftingGrid.removeItem(slot, 1) + if (residue[slot].isNotEmpty) { + craftingGrid[slot] = residue[slot] } + } - var newItem = craftingGrid[slot] - - if (newItem.isEmpty) { - when (settings.ingredientPriority) { - ItemMonitorPlayerSettings.IngredientPriority.SYSTEM -> { - if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - } - - ItemMonitorPlayerSettings.IngredientPriority.INVENTORY -> { - if (takeOne(craftingPlayer.inventory, oldItem)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - } - - ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST -> { - if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - - if (newItem.isEmpty && takeOne(craftingPlayer.inventory, oldItem)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - } - - ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST -> { - if (takeOne(craftingPlayer.inventory, oldItem)) { - newItem = oldItem - craftingGrid[slot] = newItem - } else if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) { - newItem = oldItem - craftingGrid[slot] = newItem - } - } - - ItemMonitorPlayerSettings.IngredientPriority.DO_NOT -> { /* no op */ } + if (!scanCraftingGrid(true)) { + // заполняем опустевшие слоты + for (slot in 0 until craftingGrid.containerSize) { + if ( + craftingGrid[slot].isEmpty && + copy[slot].isNotEmpty && + settings.ingredientPriority.takeOne(copy[slot], poweredView, combinedInventory, craftingGridTuples[slot]) + ) { + craftingGrid[slot] = copy[slot].copyWithCount(1) } } + } - val remainder = remainingItems[slot] + if (!scanCraftingGrid(true)) { + // избавляемся от остатков, если они мешают + for (slot in 0 until craftingGrid.containerSize) { + if (residue[slot].isNotEmpty) { + var remaining = residue[slot] - if (!remainder.isEmpty) { - when (settings.resultTarget) { - ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM, ItemMonitorPlayerSettings.ResultTarget.MIXED -> { - val remaining = poweredView?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder - - if (!remaining.isEmpty) { - if (newItem.isEmpty) { - craftingGrid[slot] = remaining - } else if (ItemStack.isSameItemSameTags(newItem, remaining)) { - newItem.grow(remaining.count) - craftingGrid.setChanged(slot) - } else if (!craftingPlayer.inventory.add(remaining)) { - craftingPlayer.drop(remaining, false) - } - } + if (settings.resultTarget != ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY) { + remaining = poweredView?.insertStack(ItemStorageStack(remaining), false)?.toItemStack() ?: remaining } - ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY -> { - if (!craftingPlayer.inventory.add(remainder)) { - val remaining = poweredView?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder + remaining = combinedInventory?.addItem(remaining, false) ?: remaining - if (!remaining.isEmpty) { - if (newItem.isEmpty) { - craftingGrid[slot] = remaining - } else if (ItemStack.isSameItemSameTags(newItem, remaining)) { - newItem.grow(remaining.count) - craftingGrid.setChanged(slot) - } else { - craftingPlayer.drop(remaining, false) - } - } - } + if (remaining.isNotEmpty) { + craftingPlayer.spawnAtLocation(remaining) + } + + if (copy[slot].isNotEmpty && settings.ingredientPriority.takeOne(copy[slot], poweredView, combinedInventory, craftingGridTuples[slot])) { + craftingGrid[slot] = copy[slot].copyWithCount(1) } } } } + + return result.copy() } finally { inProcessOfCraft = false - scanCraftingGrid() + scanCraftingGrid(false) } - - return craftingRecipe.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY).copy() } - override fun removeItemNoUpdate(p_18951_: Int): ItemStack { + override fun removeItemNoUpdate(slot: Int): ItemStack { throw UnsupportedOperationException() } @@ -464,11 +432,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte val settings = nbt.getCompound("player_settings") for (key in settings.allKeys) { - val uuid = UUID.fromString(key) - val deserialized = ItemMonitorPlayerSettings() - deserialized.deserializeNBT(settings.getCompound(key)) - - check(this.settings.put(uuid, deserialized) == null) { "Duplicate UUID??? $uuid" } + check(this.settings.put(UUID.fromString(key), ItemMonitorPlayerSettings().also { it.deserializeNBT(settings.getCompound(key)) }) == null) } craftingGrid.deserializeNBT(nbt["crafting_grid"]) @@ -480,14 +444,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte override fun tick() { super.tick() - cell.tickEnergyDemanding() - - if (craftingAmount.size != 0) - craftingAmount.clear() - - if (lastCraftingRecipe.size != 0) - lastCraftingRecipe.clear() } override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { @@ -497,6 +454,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte override fun setLevel(level: Level) { super.setLevel(level) cell.discover(this) + scanCraftingGrid(false) } override fun setRemoved() { 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 57a8530ca..0071f5dbc 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 @@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.block.entity.storage import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import it.unimi.dsi.fastutil.ints.IntAVLTreeSet +import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import net.minecraft.core.BlockPos import net.minecraft.world.entity.player.Inventory @@ -126,6 +127,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter private inner class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent { private fun updateTuple(tuple: TrackedTuple, diff: BigInteger) { + val stack = tuple.stack tuple.stack = tuple.stack.grow(diff) if (tuple.stack.isNotEmpty) { @@ -138,7 +140,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } id2tuples.remove(tuple.id) - items2tuples.remove(tuple.stack) ?: throw IllegalStateException("Cross-reference integrity check failed for $tuple") + items2tuples.remove(stack) ?: throw IllegalStateException("Cross-reference integrity check failed for $tuple") } } @@ -376,29 +378,32 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter val tuple = id2tuples[id] ?: return ItemStorageStack.EMPTY val slots = tuple.children.values.iterator() val lstack = tuple.stack + val affectedSlots = IntArrayList() - while (amount < total && slots.hasNext() && energy.batteryLevel.isPositive) { + while (amount > total && slots.hasNext() && energy.batteryLevel.isPositive) { val (slot, stack) = slots.next() - val extracted = parent.extractItem(slot, stack.count.coerceAtMost(amount.toIntSafe()), true) + val extracted = parent.extractItem(slot, stack.count.coerceAtMost((amount - total).toIntSafe()), true) val required = StorageStack.ITEMS.energyPerExtract(ItemStorageStack.unsafe(extracted)) - if (extracted.isNotEmpty && tuple.stack.equalsWithoutCount(ItemStorageStack.unsafe(extracted)) && energy.extractEnergy(required, true) == required) { + if (extracted.isNotEmpty && lstack.equalsWithoutCount(ItemStorageStack.unsafe(extracted)) && energy.extractEnergy(required, true) == required) { if (simulate) { total += extracted.count.toBigInteger() } else { + affectedSlots.add(slot) val extracted2 = parent.extractItem(slot, extracted.count, false) + total += extracted2.count.toBigInteger() if (extracted2.count == extracted.count) { energy.extractEnergy(required, false) } else { energy.extractEnergy(StorageStack.ITEMS.energyPerExtract(ItemStorageStack.unsafe(extracted2)), false) } - - total += extracted2.count.toBigInteger() } } } + val i = affectedSlots.intIterator() + while (i.hasNext()) scan(i.nextInt()) return lstack.copy(total) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt index 4a918c9b1..878e8a789 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/AndroidStationBlockEntity.kt @@ -9,7 +9,6 @@ import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.phys.AABB -import net.minecraftforge.common.ForgeConfigSpec import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.capability.MatteryCapability @@ -18,13 +17,9 @@ import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.capability.moveEnergy import ru.dbotthepony.mc.otm.config.MachinesConfig 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.ifPresentK import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.util.WriteOnce class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider { 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 7b359587c..04cf52774 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 @@ -4,13 +4,9 @@ import net.minecraft.core.BlockPos import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.item.ItemStack import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.ForgeConfigSpec import net.minecraftforge.common.ForgeHooks -import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.energy.IEnergyStorage import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.capability.* @@ -19,14 +15,9 @@ 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.core.* import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.util.WriteOnce import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.DecimalConfigValue -import ru.dbotthepony.mc.otm.core.math.defineDecimal class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.CHEMICAL_GENERATOR, pos, state) { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { 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 74f2edd4f..9d6c16322 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 @@ -4,7 +4,6 @@ import net.minecraft.core.BlockPos import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu -import net.minecraft.world.item.ItemStack import net.minecraft.world.level.block.state.BlockState import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection 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 b255cef0c..0bac233a6 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 @@ -1,7 +1,6 @@ package ru.dbotthepony.mc.otm.block.entity.tech import net.minecraft.core.BlockPos -import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt index f8e72f168..1bce3faba 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/AbstractMatteryDrive.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.capability.drive import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectArraySet +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import kotlin.jvm.JvmOverloads import java.util.UUID import net.minecraft.nbt.CompoundTag @@ -30,7 +31,7 @@ abstract class AbstractMatteryDrive> @JvmOverloads construct override var isDirty = false set(value) { - if (value != field && value && DrivePool.isLegalAccess()) { + if (value != field && value) { DrivePool.markDirty(uuid) } @@ -43,6 +44,24 @@ abstract class AbstractMatteryDrive> @JvmOverloads construct override var storedCount: BigInteger = BigInteger.ZERO protected set + protected val listeners = ObjectLinkedOpenHashSet>() + + override fun addListener(listener: IStorageEventConsumer): Boolean { + return listeners.add(listener) + } + + override fun removeListener(listener: IStorageEventConsumer): Boolean { + return listeners.remove(listener) + } + + override fun equals(other: Any?): Boolean { + return other is AbstractMatteryDrive<*> && storageType == other.storageType && uuid == other.uuid + } + + override fun hashCode(): Int { + return (uuid.hashCode() * 31) xor storageType.hashCode() + } + override fun insertStack(stack: T, simulate: Boolean): T { val maxInsert = driveCapacity.minus(storedCount).coerceAtMost(stack.count) if (maxInsert <= BigInteger.ZERO) return stack @@ -189,14 +208,4 @@ abstract class AbstractMatteryDrive> @JvmOverloads construct override val stacks: Stream> get() { return ArrayList>(stack2tuples.size).also { it.addAll(stack2tuples.values) }.stream() } - - protected val listeners = ObjectArraySet>() - - override fun addListener(listener: IStorageEventConsumer): Boolean { - return listeners.add(listener) - } - - override fun removeListener(listener: IStorageEventConsumer): Boolean { - return listeners.remove(listener) - } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt index 098f4d27d..2909d1951 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/DrivePool.kt @@ -1,15 +1,13 @@ package ru.dbotthepony.mc.otm.capability.drive -import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import java.util.UUID import net.minecraft.ReportedException import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.NbtIo import net.minecraft.CrashReport +import net.minecraft.world.level.storage.LevelResource import java.util.concurrent.locks.LockSupport -import net.minecraftforge.event.TickEvent.ServerTickEvent -import net.minecraftforge.event.TickEvent import net.minecraftforge.event.server.ServerAboutToStartEvent import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.event.level.LevelEvent @@ -19,51 +17,82 @@ import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import java.io.File import java.lang.ref.WeakReference import java.util.ArrayList +import java.util.concurrent.ConcurrentLinkedQueue + +private class WeakDriveReference(drive: IMatteryDrive<*>) { + private var drive: IMatteryDrive<*>? = drive + private val weak = WeakReference(drive) + + val isValid: Boolean get() { + return drive != null || weak.get() != null + } + + fun drive(): IMatteryDrive<*>? { + return drive ?: weak.get() + } + + fun sync(): CompoundTag? { + val drive = drive() ?: return null + + if (!drive.isDirty) { + this.drive = null + return null + } + + val tag = drive.serializeNBT() + drive.isDirty = false + this.drive = null + + return tag + } + + fun access(): IMatteryDrive<*>? { + this.drive = weak.get() + return drive + } +} + +private data class BacklogLine(val uuid: UUID, val tag: CompoundTag) /** - * Let me answer everyone's questions about: - * - * Why? - * - * There are several reasons: - * 1. This data can get very large very quickly, even when playing singleplayer (and much quicker and bigger when hosting a dedicated server) - * 2. This data can not be stored inside ItemStack.ForgeCaps due to it's size - * 3. This data can not be stored inside unshared (server only) nbt tag because, again, mods prone to use and interact with - * it wrong, causing loss of stored data or mods exposing full content of a drive inside their own tag (which cause very real "NBT size too large" - * network kicks, often locking players out of server/singleplayer worlds - * 4. [net.minecraft.world.level.saveddata.SavedData] is for storing everything inside one dat file, which - * is performance tanking, because we have to write *entire* NBT on each save, not the data of Drives that are dirty - * 5. Mods which check items for being stack-able even with stack size of 1 gonna compare nbt tag, - * which will be performance tanking due to clause 1. + * Separate thread and separate files are way faster for enormous volumes of data (if mod is being run on public dedicated server) */ object DrivePool { + private val resource = LevelResource("otm_drives") private val LOGGER = LogManager.getLogger() private val pool = Object2ObjectOpenHashMap() private var thread: Thread? = null private var stopping = false - private var exception: ReportedException? = null - private const val DATA_PATH = "data/otm_drives" - @JvmStatic - operator fun > get( - id: UUID, - deserializer: (CompoundTag) -> T, - factory: () -> T - ): T { + private val backlog = ConcurrentLinkedQueue() + private var knownBaseDirectory: File? = null + + private var serverThread: Thread? = null + + private val baseDirectory: File? get() { + val server = NULLABLE_MINECRAFT_SERVER ?: return null + + val baseDirectory = server.storageSource.getLevelPath(resource).toFile() + + if (knownBaseDirectory != baseDirectory) { + baseDirectory.mkdirs() + knownBaseDirectory = baseDirectory + } + + return baseDirectory + } + + operator fun > get(id: UUID, deserializer: (CompoundTag) -> T, factory: () -> T): T { if (!isLegalAccess()) throw IllegalAccessException("Can not access drive pool from outside of server thread.") if (!SERVER_IS_LIVE) - throw IllegalStateException("Fail-fast: Server is shutting down") + throw IllegalStateException("Server is not running") - val get = pool[id] + val get = pool[id]?.access() if (get != null) { - val accessed = get.access() - - if (accessed != null) { - return accessed as T - } + return get as T } val file = File(baseDirectory, "$id.dat") @@ -85,69 +114,9 @@ object DrivePool { return factory().also { pool[id] = WeakDriveReference(it) } } - @JvmStatic - fun put(id: UUID, drive: IMatteryDrive<*>) { - if (!isLegalAccess()) - throw IllegalAccessException("Can not access drive pool from outside of server thread.") - - if (!SERVER_IS_LIVE) - throw IllegalStateException("Fail-fast: Server is shutting down") - - pool[id] = WeakDriveReference(drive) - } - - @JvmStatic fun markDirty(id: UUID) { - if (!isLegalAccess()) - throw IllegalAccessException("Can not access drive pool from outside of server thread.") - - if (!SERVER_IS_LIVE) - throw IllegalStateException("Fail-fast: Server is shutting down") - - pool[id]?.access() - } - - private var backlog = ArrayList() - private var knownBaseDirectory: File? = null - - private val baseDirectory: File? get() { - val server = NULLABLE_MINECRAFT_SERVER ?: return null - - val baseDirectory = File(server.storageSource.worldDir.toFile(), DATA_PATH) - - if (knownBaseDirectory != baseDirectory) { - baseDirectory.mkdirs() - knownBaseDirectory = baseDirectory - } - - return baseDirectory - } - - private var serverThread: Thread? = null - - private fun thread() { - while (true) { - LockSupport.park() - - if (stopping) { - return - } - - try { - // TODO: Syncing status with main thread, since server stop can occur during work. - sync() - } catch (error: ReportedException) { - exception = error - return - } - } - } - - fun onServerPostTick(event: ServerTickEvent) { - if (event.phase == TickEvent.Phase.END) { - if (exception != null) { - throw exception!! - } + if (isLegalAccess()) { + pool[id]?.access() } } @@ -156,12 +125,8 @@ object DrivePool { * * If you are making your own drive for your own storage stack, feed code outside of server thread (e.g. client code) dummy disk. */ - @JvmStatic fun isLegalAccess(): Boolean { - if (serverThread == null) - return false - - return Thread.currentThread() === serverThread + return serverThread != null && Thread.currentThread() === serverThread } fun serverStartEvent(event: ServerAboutToStartEvent) { @@ -175,7 +140,7 @@ object DrivePool { serverThread = Thread.currentThread() stopping = false - thread = Thread(null, this::thread, "Overdrive That Matters DrivePool IO").also { it.start() } + thread = Thread(null, this::run, "Overdrive That Matters DrivePool IO").also { it.start() } } fun serverStopEvent(event: ServerStoppingEvent) { @@ -186,11 +151,8 @@ object DrivePool { LockSupport.unpark(thread) } - if (exception == null) { - writeBacklog() - sync() - } - + writeBacklog() + sync() pool.clear() } @@ -200,34 +162,26 @@ object DrivePool { private fun writeBacklog() { var needsSync = false - var removeKeys: ArrayList? = null + val removeKeys = ArrayList() for ((key, value) in pool) { - val tag = value.sync() + try { + val tag = value.sync() - if (tag != null) { - LOGGER.info("Serializing OTM Drive {}", key) - val index = backlog.indexOf(key) - - if (index != -1) { - backlog[index] = BacklogLine(key, tag, value.drive()!!) - } else { - backlog.add(BacklogLine(key, tag, value.drive()!!)) + if (tag != null) { + LOGGER.debug("Serializing OTM Drive {}", key) + backlog.add(BacklogLine(key, tag)) + needsSync = true + } else if (!value.isValid) { + removeKeys.add(key) } - - needsSync = true - } else if (!value.valid()) { - if (removeKeys == null) - removeKeys = ArrayList() - - removeKeys.add(key) + } catch (err: Throwable) { + LOGGER.error("Failed to serialize OTM Drive for persistent storage with ID $key", err) } } - if (removeKeys != null) { - for (key in removeKeys) { - pool.remove(key) - } + for (key in removeKeys) { + pool.remove(key) } if (needsSync) { @@ -235,90 +189,31 @@ object DrivePool { } } + private fun run() { + while (!stopping) { + sync() + LockSupport.park() + } + } + private fun sync() { - val copy = backlog - backlog = ArrayList() + while (true) { + val next = backlog.poll() ?: return + val (uuid, tag) = next - for (entry in copy) { try { - LOGGER.info("Syncing OTM Drive {}", entry.file) + LOGGER.debug("Syncing OTM Drive {}", uuid) - val oldFile = File(baseDirectory, entry.file.toString() + ".dat_old") + val oldFile = File(baseDirectory, "$uuid.dat_old") + val newFile = File(baseDirectory, "$uuid.dat") - if (oldFile.exists()) { - check(oldFile.delete()) { "Unable to delete old dat file" } - } + if (oldFile.exists()) check(oldFile.delete()) { "Unable to delete old dat file" } + if (newFile.exists()) check(newFile.renameTo(oldFile)) { "Unable to move old dat file" } - val newFile = File(baseDirectory, entry.file.toString() + ".dat") - - if (newFile.exists()) { - check(newFile.renameTo(oldFile)) { "Unable to move old dat file" } - } - - NbtIo.writeCompressed(entry.tag, newFile) + NbtIo.writeCompressed(tag, newFile) } catch (error: Throwable) { - val report = CrashReport("Syncing OTM Drives state to disk", error) - val category = report.addCategory("Corrupt drive") - category.setDetail("UUID", entry.file.toString()) - category.setDetail("Stored item count", entry.drive.storedCount) - category.setDetail("Capacity", entry.drive.driveCapacity) - throw ReportedException(report) + LOGGER.error("Failed to sync OTM Drive to persistent storage with ID $uuid", error) } } } } - -private class WeakDriveReference(drive: IMatteryDrive<*>) { - private var drive: IMatteryDrive<*>? = drive - private val weak = WeakReference(drive) - - fun valid(): Boolean { - return drive != null || weak.get() != null - } - - fun drive(): IMatteryDrive<*>? { - return drive ?: weak.get() - } - - fun sync(): CompoundTag? { - var drive = drive - - if (drive == null) { - drive = weak.get() - - if (drive == null) - return null - } - - if (!drive.isDirty) { - this.drive = null - return null - } - - val tag = drive.serializeNBT() - drive.isDirty = false - this.drive = null - - return tag - } - - fun access(): IMatteryDrive<*>? { - this.drive = weak.get() - return drive - } -} - -@Suppress("EqualsOrHashCode") -private data class BacklogLine(val file: UUID, val tag: CompoundTag, val drive: IMatteryDrive<*>) { - override fun equals(other: Any?): Boolean { - if (other is BacklogLine) { - return other.file == file - } - - if (other is UUID) { - return other == file - } - - return false - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt index c38dc7764..6b03bc50e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/matter/MatterPanelScreen.kt @@ -54,27 +54,10 @@ class MatterPanelScreen( val controls = DeviceControls(this, frame) - LargeBooleanRectangleButtonPanel( - this, - controls, - prop = menu.isAscendingGS, - skinElementActive = Widgets18.ARROW_UP, - skinElementInactive = Widgets18.ARROW_DOWN, - tooltipActive = TranslatableComponent("otm.gui.sorting.ascending"), - tooltipInactive = TranslatableComponent("otm.gui.sorting.descending"), - ).also { - controls.addButton(it) - } - - LargeEnumRectangleButtonPanel(this, controls, enum = ItemSorter::class.java, prop = menu.sortingGS, defaultValue = ItemSorter.DEFAULT).also { - controls.addButton(it) - it.add(ItemSorter.DEFAULT, skinElement = Widgets18.SORT_DEFAULT, tooltip = ItemSorter.DEFAULT.title) - it.add(ItemSorter.NAME, skinElement = Widgets18.SORT_ALPHABET, tooltip = ItemSorter.NAME.title) - it.add(ItemSorter.ID, skinElement = Widgets18.SORT_ID, tooltip = ItemSorter.ID.title) - it.add(ItemSorter.MOD, skinElement = Widgets18.SORT_MODID, tooltip = ItemSorter.MOD.title) - it.add(ItemSorter.MATTER_VALUE, skinElement = Widgets18.SORT_MATTER_VALUE, tooltip = ItemSorter.MATTER_VALUE.title) - it.add(ItemSorter.MATTER_COMPLEXITY, skinElement = Widgets18.SORT_MATTER_COMPLEXITY, tooltip = ItemSorter.MATTER_COMPLEXITY.title) - it.finish() + controls.sortingButtons(menu.isAscendingGS, menu.sortingGS, ItemSorter.DEFAULT) { + for (v in ItemSorter.entries) { + add(v, skinElement = v.icon, tooltip = v.title) + } } val scrollBar = DiscreteScrollBarPanel(this, frame, { @@ -156,7 +139,7 @@ class MatterPanelScreen( } } - override fun getItemStackTooltip(stack: ItemStack): List { + override fun getItemStackTooltip(stack: ItemStack): MutableList { val list = super.getItemStackTooltip(stack).toMutableList() if (isPatternView) { @@ -326,7 +309,7 @@ class MatterPanelScreen( return pattern.stack() } - override fun getItemStackTooltip(stack: ItemStack): List { + override fun getItemStackTooltip(stack: ItemStack): MutableList { return super.getItemStackTooltip(stack).toMutableList().also { it.add(TranslatableComponent( "otm.item.pattern.research", diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FramePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FramePanel.kt index 5dd1b028c..79ae5fc49 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FramePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/FramePanel.kt @@ -31,8 +31,26 @@ open class FramePanel( var onOpen: Runnable? = null, var onClose: Runnable? = null, var activeIcon: IGUIRenderable? = null, - var inactiveIcon: IGUIRenderable? = null, + var inactiveIcon: IGUIRenderable? = activeIcon, ) : AbstractButtonPanel(this@FramePanel.screen, this@FramePanel, 0f, 0f, 26f, 28f) { + constructor(panels: List>, activeIcon: IGUIRenderable? = null, inactiveIcon: IGUIRenderable? = activeIcon) : this(activeIcon = activeIcon, inactiveIcon = inactiveIcon) { + onOpen = Runnable { + for (panel in panels) { + panel.visible = true + } + } + + onClose = Runnable { + for (panel in panels) { + panel.visible = false + } + } + + if (!isActive) { + onClose!!.run() + } + } + var isActive = tabs.isEmpty() protected set diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NetworkedItemGridPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NetworkedItemGridPanel.kt new file mode 100644 index 000000000..d394b347d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/NetworkedItemGridPanel.kt @@ -0,0 +1,110 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import net.minecraft.ChatFormatting +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.chat.Component +import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.render.draw +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollBarConstants +import ru.dbotthepony.mc.otm.client.screen.storage.ItemMonitorScreen +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.integerDivisionDown +import ru.dbotthepony.mc.otm.core.util.formatReadableNumber +import ru.dbotthepony.mc.otm.core.util.formatSiComponent +import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView +import ru.dbotthepony.mc.otm.storage.StorageStack + +open class NetworkedItemGridPanel>( + screen: S, + parent: EditablePanel<*>, + val view: NetworkedItemView, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 40f, +) : EditablePanel(screen, parent, x, y, width, height) { + constructor( + screen: S, + parent: EditablePanel<*>, + view: NetworkedItemView, + x: Float = 0f, + y: Float = 0f, + width: Int, + height: Int + ) : this(screen, parent, view, x, y, width * AbstractSlotPanel.SIZE + ScrollBarConstants.WIDTH, height * AbstractSlotPanel.SIZE) + + private val slots = ArrayList() + + val canvas = object : GridPanel(screen, this@NetworkedItemGridPanel, 0f, 0f, 0f, 0f, 0, 0) { + override fun performLayout() { + super.performLayout() + + val count = columns * rows + + while (slots.size < count) { + slots.add(Slot(slots.size)) + } + + while (slots.size > count) { + slots.removeLast().remove() + } + } + } + + val scrollbar = DiscreteScrollBarPanel( + screen, + this, + { integerDivisionDown(view.itemCount, canvas.columns) }, + { _, _, _ -> } + ) + + inner class Slot(private val i: Int) : AbstractSlotPanel(screen, canvas) { + private val index get() = i + scrollbar.scroll * canvas.columns + + override val itemStack: ItemStack get() { + return view.sortedView.getOrNull(index)?.stack?.toItemStack() ?: ItemStack.EMPTY + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return scrollbar.mouseScrolledInner(x, y, scroll) + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + view.mouseClick(index, button) + return true + } + + override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + renderSlotBackground(graphics, mouseX, mouseY, partialTick) + val itemStack = view.sortedView.getOrNull(index)?.stack ?: StorageStack.ITEMS.empty + renderRegular(graphics, itemStack.toItemStack(), "") + + if (!itemStack.isEmpty) { + graphics.draw(font, itemStack.count.formatSiComponent(decimalPlaces = 1), x = width - 1f, y = height - 1f, gravity = RenderGravity.BOTTOM_RIGHT, scale = 0.75f, drawShadow = true) + } + } + + override fun getItemStackTooltip(stack: ItemStack): MutableList { + return super.getItemStackTooltip(stack).also { + it.add(TranslatableComponent("otm.gui.stored_amount", view.sortedView[index].stack.count.formatReadableNumber()).withStyle(ChatFormatting.DARK_GRAY)) + } + } + } + + init { + canvas.dock = Dock.FILL + scrollbar.dock = Dock.RIGHT + } + + override fun performLayout() { + super.performLayout() + + canvas.rows = (canvas.height / AbstractSlotPanel.SIZE).toInt() + canvas.columns = (canvas.width / AbstractSlotPanel.SIZE).toInt() + } +} 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 1bbb21a57..3d18a1491 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 @@ -29,6 +29,7 @@ import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.math.RelativeSide +import ru.dbotthepony.mc.otm.core.util.ItemSorter import ru.dbotthepony.mc.otm.core.value import ru.dbotthepony.mc.otm.menu.UpgradeSlots import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback @@ -315,7 +316,6 @@ class DeviceControls>( val upgradesButton: LargeRectangleButtonPanel? private var upgradeWindow: FramePanel? = null - private var nextY = 0f fun

> addButton(button: P): P { @@ -328,6 +328,40 @@ class DeviceControls>( return button } + fun

> prependButton(button: P): P { + for (child in children) { + child.y += button.height + 2f + } + + button.parent = this + button.x = 0f + button.y = 0f + nextY += button.height + 2f + height = nextY - 2f + width = button.width.coerceAtLeast(width) + return button + } + + inline fun > sortingButtons(ascending: GetterSetter, sorting: GetterSetter, default: T, configurator: LargeEnumRectangleButtonPanel.() -> Unit) { + LargeBooleanRectangleButtonPanel( + screen, + this, + prop = ascending, + skinElementActive = Widgets18.ARROW_UP, + skinElementInactive = Widgets18.ARROW_DOWN, + tooltipActive = TranslatableComponent("otm.gui.sorting.ascending"), + tooltipInactive = TranslatableComponent("otm.gui.sorting.descending"), + ).also { + prependButton(it) + } + + LargeEnumRectangleButtonPanel(screen, this, enum = T::class.java, prop = sorting, defaultValue = default).also { + prependButton(it) + configurator.invoke(it) + it.finish() + } + } + init { for (button in extra) { addButton(button) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt index 2b6bb7c4a..f98bbd7fe 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/slot/AbstractSlotPanel.kt @@ -42,7 +42,7 @@ abstract class AbstractSlotPanel> @JvmOverloads constru } } - protected open fun getItemStackTooltip(stack: ItemStack): List { + protected open fun getItemStackTooltip(stack: ItemStack): MutableList { return getTooltipFromItem(ru.dbotthepony.mc.otm.client.minecraft, stack) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/GridPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/GridPanel.kt index 62c17e32d..a9de349d6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/GridPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/util/GridPanel.kt @@ -4,16 +4,28 @@ import net.minecraft.client.gui.screens.Screen import ru.dbotthepony.mc.otm.client.screen.panels.Dock import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel -open class GridPanel @JvmOverloads constructor( +open class GridPanel( screen: S, parent: EditablePanel<*>?, x: Float = 0f, y: Float = 0f, - width: Float, - height: Float, - protected var columns: Int, - protected var rows: Int + width: Float = 0f, + height: Float = 0f, + columns: Int, + rows: Int ) : EditablePanel(screen, parent, x, y, width, height) { + var columns: Int = columns + set(value) { + field = value + invalidateLayout() + } + + var rows: Int = rows + set(value) { + field = value + invalidateLayout() + } + override fun performLayout() { var currentX = 0f var currentY = 0f 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 29ceb9604..c82f8b38e 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 @@ -1,23 +1,26 @@ package ru.dbotthepony.mc.otm.client.screen.storage -import net.minecraft.client.gui.GuiGraphics import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.Items +import ru.dbotthepony.mc.otm.client.render.ItemStackIcon import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.client.screen.panels.* import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel +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.FilterSlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel -import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel -import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel -import ru.dbotthepony.mc.otm.core.math.integerDivisionDown +import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel +import ru.dbotthepony.mc.otm.core.asGetterSetter +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu +import ru.dbotthepony.mc.otm.registry.MItems import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak @MouseTweaksDisableWheelTweak @@ -25,106 +28,58 @@ class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Comp MatteryScreen(menu, inventory, title) { override fun makeMainFrame(): FramePanel> { - val frame = FramePanel(this, null, 0f, 0f, FRAME_WIDTH, FRAME_HEIGHT, getTitle()) + val frame = FramePanel(this, null, 0f, 0f, 200f, 114f, getTitle()) frame.makeCloseButton() frame.onClose { onClose() } - val views = ArrayList>() + val controls = DeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig) + + controls.sortingButtons(menu.settings::isAscending.asGetterSetter(), menu.settings::sorting.asGetterSetter(), ItemStorageStackSorter.DEFAULT) { + for (v in ItemStorageStackSorter.entries) { + add(v, v.icon, v.title) + } + } + + val view = ArrayList>() val settings = ArrayList>() - frame.Tab(onOpen = { - for (panel in views) { - panel.visible = true - } - }, onClose = { - for (panel in views) { - panel.visible = false + view.add(EditablePanel(this, frame, width = AbstractSlotPanel.SIZE).also { + it.dock = Dock.LEFT + WideProfiledPowerGaugePanel(this, it, menu.profiledEnergy).also { + it.dock = Dock.TOP + it.dockBottom = 6f } + + BatterySlotPanel(this, it, menu.batterySlot).dock = Dock.TOP + SlotPanel(this, it, menu.driveSlot).dock = Dock.TOP }) - frame.Tab(onOpen = { - for (panel in settings) { - panel.visible = true - } - }, onClose = { - for (panel in settings) { - panel.visible = false - } + view.add(NetworkedItemGridPanel(this, frame, menu.networkedItemView).also { + it.dock = Dock.FILL + it.setDockMargin(4f, 0f, 0f, 0f) }) - views.add(PowerGaugePanel(this, frame, menu.energyWidget, 8f, 16f)) - views.add(BatterySlotPanel(this, frame, menu.batterySlot, 8f, 67f)) - views.add(SlotPanel(this, frame, menu.driveSlot, 8f, 85f)) - - val grid = GridPanel(this, frame, 28f, 16f, GRID_WIDTH * 18f, GRID_HEIGHT * 18f, GRID_WIDTH, GRID_HEIGHT) - - val scrollBar = DiscreteScrollBarPanel(this, frame, { integerDivisionDown(menu.networkedItemView.itemCount, GRID_WIDTH) }, { _, _, _ -> }, 192f, 14f, 92f) - - views.add(grid) - views.add(scrollBar) - - for (i in 0 until GRID_WIDTH * GRID_HEIGHT) { - object : AbstractSlotPanel(this@DriveViewerScreen, grid, 0f, 0f) { - override val itemStack: ItemStack get() { - val index = i + scrollBar.scroll * GRID_WIDTH - return menu.networkedItemView.sortedView.getOrNull(index)?.stack?.toItemStack() ?: ItemStack.EMPTY - } - - override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return scrollBar.mouseScrolledInner(x, y, scroll) - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - val index = i + scrollBar.scroll * GRID_WIDTH - menu.networkedItemView.mouseClick(index, button) - return true - } - - override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { - renderSlotBackground(graphics, mouseX, mouseY, partialTick) - renderRegular(graphics, itemStack, "") - } - - override fun getItemStackTooltip(stack: ItemStack): List { - return super.getItemStackTooltip(stack).also { - it as MutableList - val index = i + scrollBar.scroll * GRID_WIDTH - val realStack = menu.networkedItemView.sortedView.getOrNull(index)!!.stack - it.add(TranslatableComponent("otm.gui.item_amount", realStack.count.toString())) - } - } - } - } - - val dock_left = FlexGridPanel(this, frame, 0f, 0f, 90f, 0f) - dock_left.dock = Dock.LEFT - dock_left.setDockMargin(4f, 0f, 4f, 0f) - - val grid_filter = FlexGridPanel(this, frame) - grid_filter.dock = Dock.FILL - settings.add(dock_left) - settings.add(grid_filter) + val filterGrid = GridPanel(this, frame, width = AbstractSlotPanel.SIZE * 3f, height = AbstractSlotPanel.SIZE * 4f, rows = 3, columns = 4) + filterGrid.dock = Dock.FILL + filterGrid.dockResize = DockResizeMode.NONE + filterGrid.dockTop = 20f + settings.add(filterGrid) for (i in 0 until PortableCondensationDriveItem.MAX_FILTERS) { - FilterSlotPanel(this, grid_filter, menu.driveFilterSlots[i], 0f, 0f) + FilterSlotPanel(this, filterGrid, menu.driveFilterSlots[i], 0f, 0f) } - CheckBoxLabelInputPanel(this, dock_left, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) } - CheckBoxLabelInputPanel(this, dock_left, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) } - CheckBoxLabelInputPanel(this, dock_left, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) } + settings.add(EditablePanel(this, frame, width = 90f).also { + it.dock = Dock.LEFT + CheckBoxLabelInputPanel(this, it, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { it.dockTop = 20f; it.dock = Dock.TOP } + CheckBoxLabelInputPanel(this, it, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { it.dockTop = 4f; it.dock = Dock.TOP } + CheckBoxLabelInputPanel(this, it, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt")).also { it.dockTop = 4f; it.dock = Dock.TOP } + }) - for (panel in settings) { - panel.visible = false - } + frame.Tab(view, activeIcon = ItemStackIcon(ItemStack(MItems.PORTABLE_CONDENSATION_DRIVE))) + frame.Tab(settings, activeIcon = ItemStackIcon(ItemStack(Items.HOPPER))) return frame } - - companion object { - const val FRAME_WIDTH = 210f - const val FRAME_HEIGHT = 110f - const val GRID_WIDTH = 9 - const val GRID_HEIGHT = 5 - } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/ItemMonitorScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/ItemMonitorScreen.kt index 4215dc9cf..d1cfbde67 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/ItemMonitorScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/storage/ItemMonitorScreen.kt @@ -1,23 +1,21 @@ package ru.dbotthepony.mc.otm.client.screen.storage -import com.mojang.blaze3d.systems.RenderSystem -import com.mojang.blaze3d.vertex.PoseStack -import net.minecraft.ChatFormatting import net.minecraft.client.gui.GuiGraphics import net.minecraft.network.chat.Component import net.minecraft.world.entity.player.Inventory import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items -import org.lwjgl.opengl.GL11 -import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings -import ru.dbotthepony.mc.otm.client.render.UVWindingOrder -import ru.dbotthepony.mc.otm.client.render.Widgets8 +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 import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel +import ru.dbotthepony.mc.otm.client.screen.panels.NetworkedItemGridPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeBooleanRectangleButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeEnumRectangleButtonPanel import ru.dbotthepony.mc.otm.client.screen.panels.button.SmallEnumRectangleButtonPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel @@ -27,12 +25,10 @@ import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel +import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.asGetterSetter -import ru.dbotthepony.mc.otm.core.math.integerDivisionDown -import ru.dbotthepony.mc.otm.core.util.formatReadableNumber -import ru.dbotthepony.mc.otm.core.util.formatSiComponent +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter import ru.dbotthepony.mc.otm.menu.storage.ItemMonitorMenu -import ru.dbotthepony.mc.otm.storage.StorageStack import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak @MouseTweaksDisableWheelTweak @@ -57,71 +53,17 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp frame.height = topPanel.height + bottomPanel.height + frame.dockPadding.top + frame.dockPadding.bottom + 3f frame.width = 178f + frame.dockPadding.left + frame.dockPadding.right - val viewScrollBar = DiscreteScrollBarPanel(this, topPanel, - { integerDivisionDown(menu.networkedItemView.itemCount, ITEM_GRID_WIDTH) }, - { _, _, _ -> }, - 28f + ITEM_GRID_WIDTH * 18f + 2f, 16f, ITEM_GRID_HEIGHT * 18f) + val controls = DeviceControls(this, frame) - viewScrollBar.dock = Dock.RIGHT - viewScrollBar.setDockMargin(left = 2f) - - val gridPanel = GridPanel(this, topPanel, width = ITEM_GRID_WIDTH * 18f, height = ITEM_GRID_HEIGHT * 18f, columns = ITEM_GRID_WIDTH, rows = ITEM_GRID_HEIGHT) - gridPanel.dock = Dock.FILL - - for (i in 0 until ITEM_GRID_WIDTH * ITEM_GRID_HEIGHT) { - object : AbstractSlotPanel(this@ItemMonitorScreen, gridPanel) { - private val index get() = i + viewScrollBar.scroll * ITEM_GRID_WIDTH - - override val itemStack: ItemStack get() { - return menu.networkedItemView.sortedView.getOrNull(index)?.stack?.toItemStack() ?: ItemStack.EMPTY - } - - override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { - return viewScrollBar.mouseScrolledInner(x, y, scroll) - } - - override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { - menu.networkedItemView.mouseClick(index, button) - return true - } - - override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { - renderSlotBackground(graphics, mouseX, mouseY, partialTick) - - val itemstack = menu.networkedItemView.sortedView.getOrNull(index)?.stack ?: StorageStack.ITEMS.empty - val stack = graphics.pose() - - renderRegular(graphics, itemstack.toItemStack(), "") - - if (!itemstack.isEmpty) { - stack.pushPose() - - val text = itemstack.count.formatSiComponent(decimalPlaces = 1) - val textWidth = font.width(text) - - stack.translate((width - textWidth * FONT_SCALE).toDouble(), (height - font.lineHeight * FONT_SCALE).toDouble(), 103.0) - - stack.scale(FONT_SCALE, FONT_SCALE, 1f) - - RenderSystem.depthFunc(GL11.GL_ALWAYS) - - graphics.drawString(font, text, 1, 1, 0x0) - graphics.drawString(font, text, 0, 0, 16777215) - - stack.popPose() - } - } - - override fun getItemStackTooltip(stack: ItemStack): List { - return super.getItemStackTooltip(stack).also { - it as MutableList - val realStack = menu.networkedItemView.sortedView.getOrNull(index)!!.stack - it.add(TranslatableComponent("otm.gui.stored_amount", realStack.count.formatReadableNumber()).withStyle(ChatFormatting.DARK_GRAY)) - } - } + controls.sortingButtons(menu.settings::ascendingSort.asGetterSetter(), menu.settings::sorting.asGetterSetter(), ItemStorageStackSorter.DEFAULT) { + for (v in ItemStorageStackSorter.entries) { + add(v, skinElement = v.icon, tooltip = v.title) } } + val grid = NetworkedItemGridPanel(this, topPanel, menu.networkedItemView) + grid.dock = Dock.FILL + val craftingGrid = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3) craftingGrid.dock = Dock.LEFT @@ -142,19 +84,19 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp val arrowLine = EditablePanel(this, arrowAndButtons, y = 38f, height = 8f, width = arrowAndButtons.width) - val refillPriority = SmallEnumRectangleButtonPanel(this, arrowLine, + SmallEnumRectangleButtonPanel(this, arrowLine, enum = ItemMonitorPlayerSettings.IngredientPriority::class.java, - prop = menu.settings::ingredientPriority.asGetterSetter(watch = { _, _ -> menu.sendSettingsToServer() }), + prop = menu.settings::ingredientPriority.asGetterSetter(), defaultValue = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM) + .also { + it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.refill_source.desc")) - refillPriority.tooltips.add(TranslatableComponent("otm.gui.item_monitor.refill_source.desc")) - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.SYSTEM, tooltip = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM.component, skinElement = Widgets8.WHITE_ARROW_DOWN, winding = UVWindingOrder.FLIP) - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.INVENTORY, tooltip = ItemMonitorPlayerSettings.IngredientPriority.INVENTORY.component, skinElement = Widgets8.WHITE_ARROW_DOWN) - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST, tooltip = ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST.component, skinElement = Widgets8.ARROW_SIDEWAYS, winding = UVWindingOrder.FLIP) - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST, tooltip = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST.component, skinElement = Widgets8.ARROW_SIDEWAYS) - refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.DO_NOT, tooltip = ItemMonitorPlayerSettings.IngredientPriority.DO_NOT.component, skinElement = Widgets8.MINUS) + for (setting in ItemMonitorPlayerSettings.IngredientPriority.entries) { + it.add(setting, setting.icon, setting.component, setting.winding) + } - refillPriority.dock = Dock.LEFT + it.dock = Dock.LEFT + } val resultAndButtons = EditablePanel(this, bottomPanel, width = 18f) @@ -163,25 +105,29 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp SlotPanel(this, resultAndButtons, menu.craftingResult, y = 18f) - val resultTarget = SmallEnumRectangleButtonPanel(this, resultAndButtons, y = 38f, + SmallEnumRectangleButtonPanel(this, resultAndButtons, y = 38f, enum = ItemMonitorPlayerSettings.ResultTarget::class.java, - prop = menu.settings::resultTarget.asGetterSetter(watch = { _, _ -> menu.sendSettingsToServer() }), + prop = menu.settings::resultTarget.asGetterSetter(), defaultValue = ItemMonitorPlayerSettings.ResultTarget.MIXED) + .also { + it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.result_target.desc")) - resultTarget.tooltips.add(TranslatableComponent("otm.gui.item_monitor.result_target.desc")) - resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.MIXED, tooltip = ItemMonitorPlayerSettings.ResultTarget.MIXED.component, skinElement = Widgets8.ARROW_SIDEWAYS) - resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY, tooltip = ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY.component, skinElement = Widgets8.ARROW_PAINTED_UP, winding = UVWindingOrder.FLIP) - resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM, tooltip = ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM.component, skinElement = Widgets8.ARROW_PAINTED_UP) + for (setting in ItemMonitorPlayerSettings.ResultTarget.entries) { + it.add(setting, setting.icon, setting.component, setting.winding) + } + } - val craftingAmount = SmallEnumRectangleButtonPanel(this, resultAndButtons, x = 10f, y = 38f, + SmallEnumRectangleButtonPanel(this, resultAndButtons, x = 10f, y = 38f, enum = ItemMonitorPlayerSettings.Amount::class.java, - prop = menu.settings::craftingAmount.asGetterSetter(watch = { _, _ -> menu.sendSettingsToServer() }), + prop = menu.settings::craftingAmount.asGetterSetter(), defaultValue = ItemMonitorPlayerSettings.Amount.STACK) + .also { + it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.amount.desc")) - craftingAmount.tooltips.add(TranslatableComponent("otm.gui.item_monitor.amount.desc")) - craftingAmount.add(ItemMonitorPlayerSettings.Amount.ONE, tooltip = ItemMonitorPlayerSettings.Amount.ONE.component, skinElement = Widgets8.ONE) - craftingAmount.add(ItemMonitorPlayerSettings.Amount.STACK, tooltip = ItemMonitorPlayerSettings.Amount.STACK.component, skinElement = Widgets8.S) - craftingAmount.add(ItemMonitorPlayerSettings.Amount.FULL, tooltip = ItemMonitorPlayerSettings.Amount.FULL.component, skinElement = Widgets8.F) + for (setting in ItemMonitorPlayerSettings.Amount.entries) { + it.add(setting, setting.icon, setting.component, setting.winding) + } + } val craftingHistory = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3) craftingHistory.dock = Dock.LEFT @@ -227,7 +173,5 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp companion object { const val ITEM_GRID_WIDTH = 9 const val ITEM_GRID_HEIGHT = 5 - - const val FONT_SCALE = 0.6f } } 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 583b1acb0..4653d9e97 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt @@ -139,10 +139,10 @@ object MachinesConfig : AbstractConfig("machines") { } val STORAGE_POWER_SUPPLIER = conciseValues(MNames.STORAGE_POWER_SUPPLIER, Decimal(80_000), Decimal(2_000)) - val STORAGE_INTERFACES = conciseValues("STORAGE_INTERFACES", Decimal(10_000), Decimal(100)) - val ITEM_MONITOR = conciseValues(MNames.ITEM_MONITOR, Decimal(10_000), Decimal(100)) - val DRIVE_VIEWER = conciseValues(MNames.DRIVE_VIEWER, Decimal(10_000), Decimal(100)) - val DRIVE_RACK = conciseValues(MNames.DRIVE_RACK, Decimal(10_000), Decimal(100)) + val STORAGE_INTERFACES = conciseValues("STORAGE_INTERFACES", Decimal(10_000), Decimal(10_000)) + val ITEM_MONITOR = conciseValues(MNames.ITEM_MONITOR, Decimal(10_000), Decimal(10_000)) + val DRIVE_VIEWER = conciseValues(MNames.DRIVE_VIEWER, Decimal(10_000), Decimal(10_000)) + val DRIVE_RACK = conciseValues(MNames.DRIVE_RACK, Decimal(10_000), Decimal(10_000)) object AndroidCharger { val RADIUS_WIDTH: Double by builder 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 90c253d10..7d372b6ca 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/CombinedContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/CombinedContainer.kt @@ -12,6 +12,7 @@ import net.minecraft.world.Container import net.minecraft.world.entity.player.Player import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.collect.concatIterators import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.collect.flatMap import ru.dbotthepony.mc.otm.core.collect.map @@ -167,10 +168,10 @@ class CombinedContainer(containers: Stream>>) : Co } fun optimizedIterator(): Iterator { - return listOf( + return concatIterators( fullCoverage.iterator().flatMap { it.iterator() }, notFullCoverage.values.iterator().flatMap { it.iterator() }.map { it.item }.filter { it.isNotEmpty } - ).iterator().flatMap { it } + ) } class Builder { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt index 1269f480a..fcf660f9c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ContainerIterator.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.container +import it.unimi.dsi.fastutil.objects.ObjectIterators import net.minecraft.world.Container import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.core.isNotEmpty @@ -50,3 +51,32 @@ fun Container.iterator(): IContainerIterator { ContainerIterator(this) } } + +class FullContainerIterator(val container: Container, initialPos: Int = 0) : MutableIterator, ObjectIterators.AbstractIndexBasedListIterator(0, initialPos), IContainerIterator { + override fun remove(location: Int) { + pos++ + container[location] = ItemStack.EMPTY + } + + override fun get(location: Int): ItemStack { + return container[location] + } + + override fun getMaxPos(): Int { + return container.containerSize + } + + override fun add(location: Int, k: ItemStack?) { + throw UnsupportedOperationException() + } + + override fun set(location: Int, k: ItemStack) { + container[location] = k + } + + override fun setChanged() { + container.setChanged() + } +} + +fun Container.fullIterator() = FullContainerIterator(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 17ad146f5..239d59990 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -53,6 +53,7 @@ import java.util.Arrays import java.util.Spliterators import java.util.UUID import java.util.function.Consumer +import java.util.function.Supplier import java.util.stream.Stream import java.util.stream.StreamSupport import kotlin.reflect.KProperty @@ -367,12 +368,20 @@ fun Comparator.nullsLast(): Comparator { return Comparator.nullsLast(this as Comparator) } +fun Comparator.map(mapper: (B) -> A): Comparator { + return Comparator { a, b -> this@map.compare(mapper.invoke(a), mapper.invoke(b)) } +} + +fun Comparator.suppliers(): Comparator> { + return Comparator { o1, o2 -> this@suppliers.compare(o1.get(), o2.get()) } +} + /** * Returns applicable index to put [element] into [List] determined by [comparator], optionally specifying ranges as [fromIndex] and [toIndex] * * If [List] is not sorted, result of this function is undefined */ -fun List.searchInsertionIndex(element: E, comparator: Comparator, fromIndex: Int = 0, toIndex: Int = size): Int { +fun List.searchInsertionIndex(element: E, comparator: Comparator, fromIndex: Int = 0, toIndex: Int = size): Int { require(toIndex >= fromIndex) { "Invalid range: to $toIndex >= from $fromIndex" } require(fromIndex >= 0) { "Invalid from index: $fromIndex" } require(toIndex >= 0) { "Invalid to index: $toIndex" } @@ -421,7 +430,7 @@ fun List.searchInsertionIndex(element: E, comparator: Comparator, from * * If [MutableList] is not sorted, result of this function is undefined */ -fun MutableList.addSorted(element: E, comparator: Comparator) { +fun MutableList.addSorted(element: E, comparator: Comparator) { add(searchInsertionIndex(element, comparator), element) } @@ -433,3 +442,8 @@ fun MutableList.addSorted(element: E, comparator: Comparator) { fun > MutableList.addSorted(element: E) { add(searchInsertionIndex(element, ObjectComparators.NATURAL_COMPARATOR), element) } + +fun lazy2(a: () -> A, b: A.() -> B): Supplier { + val first = lazy(a) + return Supplier { b.invoke(first.value) } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/GetterSetter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/GetterSetter.kt index 700b2c868..258840f95 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/GetterSetter.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/GetterSetter.kt @@ -93,8 +93,14 @@ interface GetterSetter : Supplier, Consumer, ReadWriteProperty } } - fun box(value: V): GetterSetter { - return object : GetterSetter { + fun box(value: V): SentientGetterSetter { + return object : SentientGetterSetter { + private val subs = ISubscriptable.Impl() + + override fun addListener(listener: Consumer): ISubscriptable.L { + return subs.addListener(listener) + } + private var value = value override fun get(): V { @@ -103,12 +109,15 @@ interface GetterSetter : Supplier, Consumer, ReadWriteProperty override fun accept(t: V) { this.value = t + subs.accept(t) } } } } } +interface SentientGetterSetter : GetterSetter, ISubscriptable + operator fun Supplier.getValue(thisRef: Any?, property: KProperty<*>): T { return get() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ISubscripable.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ISubscripable.kt new file mode 100644 index 000000000..ff17bd269 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ISubscripable.kt @@ -0,0 +1,213 @@ +package ru.dbotthepony.mc.otm.core + +import it.unimi.dsi.fastutil.booleans.BooleanConsumer +import it.unimi.dsi.fastutil.floats.FloatConsumer +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet +import java.util.function.Consumer +import java.util.function.DoubleConsumer +import java.util.function.IntConsumer +import java.util.function.LongConsumer + +interface ISubscriptable { + /** + * Listener token, allows to remove listener from subscriber list + */ + fun interface L { + /** + * Removes this listener + */ + fun remove() + } + + fun addListener(listener: Consumer): L + + class Impl : ISubscriptable, Consumer { + private inner class L(val callback: Consumer) : ISubscriptable.L { + init { + subscribers.add(this) + } + + override fun remove() { + subscribers.remove(this) + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + + override fun addListener(listener: Consumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: V) { + subscribers.forEach { it.callback.accept(t) } + } + } + + companion object : ISubscriptable, L { + @Suppress("unchecked_cast") + fun empty(): ISubscriptable { + return this as ISubscriptable + } + + override fun remove() {} + + override fun addListener(listener: Consumer): L { + return this + } + } +} + +interface IFloatSubcripable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(listener::accept) + } + + fun addListener(listener: FloatConsumer): ISubscriptable.L + + class Impl : IFloatSubcripable, Consumer, FloatConsumer { + private inner class L(val callback: FloatConsumer) : ISubscriptable.L { + init { + subscribers.add(this) + } + + override fun remove() { + subscribers.remove(this) + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + + override fun addListener(listener: FloatConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Float) { + subscribers.forEach { it.callback.accept(t) } + } + } +} + +interface IDoubleSubcripable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(DoubleConsumer { listener.accept(it) }) + } + + fun addListener(listener: DoubleConsumer): ISubscriptable.L + + class Impl : IDoubleSubcripable, Consumer, DoubleConsumer { + private inner class L(val callback: DoubleConsumer) : ISubscriptable.L { + init { + subscribers.add(this) + } + + override fun remove() { + subscribers.remove(this) + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + + override fun addListener(listener: DoubleConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Double) { + subscribers.forEach { it.callback.accept(t) } + } + } +} + +interface IIntSubcripable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(IntConsumer { listener.accept(it) }) + } + + fun addListener(listener: IntConsumer): ISubscriptable.L + + class Impl : IIntSubcripable, Consumer, IntConsumer { + private inner class L(val callback: IntConsumer) : ISubscriptable.L { + init { + subscribers.add(this) + } + + override fun remove() { + subscribers.remove(this) + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + + override fun addListener(listener: IntConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Int) { + subscribers.forEach { it.callback.accept(t) } + } + } +} + +interface ILongSubcripable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(LongConsumer { listener.accept(it) }) + } + + fun addListener(listener: LongConsumer): ISubscriptable.L + + class Impl : ILongSubcripable, Consumer, LongConsumer { + private inner class L(val callback: LongConsumer) : ISubscriptable.L { + init { + subscribers.add(this) + } + + override fun remove() { + subscribers.remove(this) + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + + override fun addListener(listener: LongConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Long) { + subscribers.forEach { it.callback.accept(t) } + } + } +} + +interface IBooleanSubscriptable : ISubscriptable { + @Deprecated("Use type specific listener") + override fun addListener(listener: Consumer): ISubscriptable.L { + return addListener(listener::accept) + } + + fun addListener(listener: BooleanConsumer): ISubscriptable.L + + class Impl : IBooleanSubscriptable, Consumer, BooleanConsumer { + private inner class L(val callback: BooleanConsumer) : ISubscriptable.L { + init { + subscribers.add(this) + } + + override fun remove() { + subscribers.remove(this) + } + } + + private val subscribers = ReferenceLinkedOpenHashSet(0) + + override fun addListener(listener: BooleanConsumer): ISubscriptable.L { + return L(listener) + } + + override fun accept(t: Boolean) { + subscribers.forEach { it.callback.accept(t) } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterator.kt index f8cbb49af..4b11f9329 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterator.kt @@ -251,7 +251,7 @@ fun Iterator.mapToDouble(mapper: O2DFunction): it.unimi.dsi.fastutil.d } override fun remove() { - throw UnsupportedOperationException() + (this@mapToDouble as MutableIterator).remove() } override fun nextDouble(): Double { @@ -267,7 +267,7 @@ fun Iterator.mapToInt(mapper: O2IFunction): it.unimi.dsi.fastutil.ints } override fun remove() { - throw UnsupportedOperationException() + (this@mapToInt as MutableIterator).remove() } override fun nextInt(): Int { @@ -362,7 +362,7 @@ fun Iterator.collect(collector: Collector): R { return collector.finisher().apply(instance) } -fun Iterator.toList(): List { +fun Iterator.toList(): MutableList { val result = ArrayList() result.addAll(this) return result 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 538382561..277b58bf5 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 @@ -71,7 +71,12 @@ inline fun CompoundTag.mapPresent(key: String, consumer: (T fun CompoundTag.mapString(index: String, mapper: (String) -> T, orElse: T): T { val tag = this[index] as? StringTag ?: return orElse - return mapper.invoke(tag.asString) + + return try { + mapper.invoke(tag.asString) + } catch (err: NoSuchElementException) { + orElse + } } fun CompoundTag.getItemStack(key: String): ItemStack = map(key, ItemStack::of) ?: ItemStack.EMPTY diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt index 49aee5756..a773fb4d1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Formatting.kt @@ -96,7 +96,10 @@ fun BigInteger.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, forma buffer[add + i + divided.length + 1] = prefix.paddedIndex(remainder, i) } - return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix) + if (suffix == "") + return TranslatableComponent(prefix.conciseFormatLocaleKey, String(buffer)) + else + return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix) } private val never = BooleanSupplier { false } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt index a2034d840..3cf948b94 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt @@ -3,17 +3,31 @@ package ru.dbotthepony.mc.otm.core.util import it.unimi.dsi.fastutil.objects.Reference2IntFunction import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap import net.minecraft.network.chat.Component +import net.minecraft.network.chat.MutableComponent import net.minecraft.world.item.CreativeModeTabs import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack import net.minecraftforge.common.CreativeModeTabRegistry import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.client.render.IGUIRenderable import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.nullsFirst import ru.dbotthepony.mc.otm.core.nullsLast import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.suppliers import ru.dbotthepony.mc.otm.matter.MatterManager +import ru.dbotthepony.mc.otm.storage.ItemStorageStack +import ru.dbotthepony.mc.otm.client.render.Widgets18 -object CreativeMenuComparator : Comparator { +private fun Comparator.stacks(): Comparator { + return Comparator { o1, o2 -> this@stacks.compare(o1.item, o2.item) }.nullsFirst() +} + +private fun Comparator.storage(): Comparator { + return Comparator { o1, o2 -> this@storage.compare(o1.item, o2.item) }.nullsFirst() +} + +object CreativeMenuItemComparator : Comparator { override fun compare(o1: Item, o2: Item): Int { rebuild() return item2index.getInt(o1).compareTo(item2index.getInt(o2)) @@ -84,6 +98,24 @@ object ItemLocalizedNameComparator : Comparator { val NullsLast = nullsLast() } +object ItemStackNameComparator : Comparator { + override fun compare(o1: ItemStack, o2: ItemStack): Int { + return o1.hoverName.string.compareTo(o2.hoverName.string) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemStorageStackNameComparator : Comparator { + override fun compare(o1: ItemStorageStack, o2: ItemStorageStack): Int { + return o1.hoverName.string.compareTo(o2.hoverName.string) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + object ItemModComparator : Comparator { override fun compare(o1: Item, o2: Item): Int { val a = o1.registryName?.namespace ?: return 0 @@ -106,14 +138,65 @@ object ItemIDComparator : Comparator { val NullsLast = nullsLast() } -enum class ItemSorter(val comparator: Comparator, private val sTitle: Component) { - DEFAULT(CreativeMenuComparator.NullsFirst, TranslatableComponent("otm.gui.sorting.default")), - NAME(ItemLocalizedNameComparator.NullsFirst.thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.name")), - ID(ItemIDComparator.NullsFirst.thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.id")), - MOD(ItemModComparator.NullsFirst.thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.modid")), - MATTER_VALUE(MatterValueComparator.NullsFirst.thenComparing(MatterComplexityComparator.NullsFirst).thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.matter_value")), - MATTER_COMPLEXITY(MatterComplexityComparator.NullsFirst.thenComparing(MatterValueComparator.NullsFirst).thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.matter_complexity")), +object ItemStackCountComparator : Comparator { + override fun compare(o1: ItemStack, o2: ItemStack): Int { + return o1.count.compareTo(o2.count) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemStorageStackCountComparator : Comparator { + override fun compare(o1: ItemStorageStack, o2: ItemStorageStack): Int { + return o1.count.compareTo(o2.count) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +enum class ItemSorter(comparator: Comparator, private val sTitle: Component, icon: Lazy) : Comparator by comparator.nullsFirst() { + DEFAULT(CreativeMenuItemComparator, TranslatableComponent("otm.gui.sorting.default"), lazy { Widgets18.SORT_DEFAULT }), + NAME(ItemLocalizedNameComparator.thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.name"), lazy { Widgets18.SORT_ALPHABET }), + ID(ItemIDComparator.thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.id"), lazy { Widgets18.SORT_ID }), + MOD(ItemModComparator.thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.modid"), lazy { Widgets18.SORT_MODID }), + MATTER_VALUE(MatterValueComparator.thenComparing(MatterComplexityComparator).thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.matter_value"), lazy { Widgets18.SORT_MATTER_VALUE }), + MATTER_COMPLEXITY(MatterComplexityComparator.thenComparing(MatterValueComparator).thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.matter_complexity"), lazy { Widgets18.SORT_MATTER_COMPLEXITY }), ; - val title: Component get() = sTitle.copy() + val icon: IGUIRenderable by icon + val title: MutableComponent get() = sTitle.copy() + val suppliers = suppliers() + val reversed: Comparator = reversed() +} + +enum class ItemStackSorter(comparator: Comparator, private val sTitle: Component, icon: Lazy) : Comparator by comparator { + DEFAULT(ItemSorter.DEFAULT.stacks(), ItemSorter.DEFAULT.title, lazy { Widgets18.SORT_DEFAULT }), + COUNT(ItemStackCountComparator.NullsFirst.thenComparing(ItemSorter.DEFAULT.stacks()), TranslatableComponent("otm.gui.sorting.count"), lazy { Widgets18.SORT_COUNT }), + NAME(ItemStackNameComparator.NullsFirst.thenComparing(ItemSorter.DEFAULT.stacks()), ItemSorter.NAME.title, lazy { Widgets18.SORT_ALPHABET }), + ID(ItemSorter.ID.stacks(), ItemSorter.ID.title, lazy { Widgets18.SORT_ID }), + MOD(ItemSorter.MOD.stacks(), ItemSorter.MOD.title, lazy { Widgets18.SORT_MODID }), + MATTER_VALUE(ItemSorter.MATTER_VALUE.stacks(), ItemSorter.MATTER_VALUE.title, lazy { Widgets18.SORT_MATTER_VALUE }), + MATTER_COMPLEXITY(ItemSorter.MATTER_COMPLEXITY.stacks(), ItemSorter.MATTER_COMPLEXITY.title, lazy { Widgets18.SORT_MATTER_COMPLEXITY }); + + val icon: IGUIRenderable by icon + val title: MutableComponent get() = sTitle.copy() + val suppliers = suppliers() + val reversed: Comparator = reversed() +} + +enum class ItemStorageStackSorter(comparator: Comparator, private val sTitle: Component, icon: Lazy) : Comparator by comparator { + DEFAULT(ItemSorter.DEFAULT.storage(), ItemSorter.DEFAULT.title, lazy { Widgets18.SORT_DEFAULT }), + COUNT(ItemStorageStackCountComparator.NullsFirst.thenComparing(ItemSorter.DEFAULT.storage()), TranslatableComponent("otm.gui.sorting.count"), lazy { Widgets18.SORT_COUNT }), + NAME(ItemStorageStackNameComparator.NullsFirst.thenComparing(ItemSorter.DEFAULT.storage()), ItemSorter.NAME.title, lazy { Widgets18.SORT_ALPHABET }), + ID(ItemSorter.ID.storage(), ItemSorter.ID.title, lazy { Widgets18.SORT_ID }), + MOD(ItemSorter.MOD.storage(), ItemSorter.MOD.title, lazy { Widgets18.SORT_MODID }), + MATTER_VALUE(ItemSorter.MATTER_VALUE.storage(), ItemSorter.MATTER_VALUE.title, lazy { Widgets18.SORT_MATTER_VALUE }), + MATTER_COMPLEXITY(ItemSorter.MATTER_COMPLEXITY.storage(), ItemSorter.MATTER_COMPLEXITY.title, lazy { Widgets18.SORT_MATTER_COMPLEXITY }); + + val icon: IGUIRenderable by icon + val title: MutableComponent get() = sTitle.copy() + val suppliers = suppliers() + val reversed: Comparator = reversed() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt index c044c136f..2789e4ebf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/SiPrefix.kt @@ -33,6 +33,7 @@ enum class SiPrefix(val power: Int, val symbol: String) { open val isEmpty: Boolean get() = false val formatLocaleKey = "otm.suffix.${name.lowercase()}".intern() + val conciseFormatLocaleKey = "otm.suffix_concise.${name.lowercase()}".intern() val rawLocaleKey = "otm.suffix_raw.${name.lowercase()}".intern() val string: String diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt index 13df85dc2..7062a7054 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.core.util +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.core.addSorted @@ -15,6 +16,7 @@ class TickList : ITickable { private val toRemoveFromAlways = ArrayList() private val timers = ArrayDeque() + private val namedTimers = Object2ObjectOpenHashMap(0) var inTicker = false private set @@ -42,6 +44,20 @@ class TickList : ITickable { } } + /** + * Calling this method while timer already exists removes old timer + */ + fun namedTimer(name: Any?, ticks: Int, callback: Runnable): Timer { + val remove = namedTimers.remove(name) + + if (remove != null) + timers.remove(remove) + + val timer = Timer(ticks, callback) + namedTimers[name] = timer + return timer + } + inner class Ticker(parent: ITickable) : ITickable by parent { init { add(this, always, alwaysQueued) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt index bc0f21f30..d00cf71db 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.graph.storage +import it.unimi.dsi.fastutil.objects.ObjectArraySet import net.minecraft.world.level.block.entity.BlockEntity import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage @@ -13,7 +14,7 @@ import java.util.* import java.util.function.Supplier open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null) : GraphNode(::StorageGraph) { - protected val components = ArrayList>() + protected val components = ObjectArraySet>() /** * Setting this to true will render non functional default [attachComponents], @@ -74,32 +75,15 @@ open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null } } - @Suppress("unchecked_cast") - fun , U : IStorage> computeIfAbsent(type: StorageStack.Type, provider: (StorageStack.Type) -> U): U { - return components.firstOrNull { it.storageType == type } as U? ?: provider(type).also { addStorageComponent(it) } - } - fun addStorageComponent(component: IStorage<*>) { - if (!components.any { component === it || it.storageType === component.storageType }) { - components.add(component) - + if (components.add(component)) if (isValid && !manualAttaching) graph.add(component) - } } fun removeStorageComponent(component: IStorage<*>) { - val indexOf = components.indexOfFirst { component === it || it.storageType === component.storageType } - if (indexOf == -1) return - val self = components.removeAt(indexOf) - if (isValid) graph.remove(self) - } - - fun removeStorageComponent(component: StorageStack.Type<*>) { - val indexOf = components.indexOfFirst { it.storageType === component } - if (indexOf == -1) return - val self = components.removeAt(indexOf) - if (isValid) graph.remove(self) + if (components.remove(component) && isValid) + graph.remove(component) } override fun invalidate() { 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 db3244d76..5a0bc74e8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -54,6 +54,7 @@ import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.MenuFieldPacket import ru.dbotthepony.mc.otm.network.MenuNetworkChannel +import ru.dbotthepony.mc.otm.network.SetCarriedPacket import ru.dbotthepony.mc.otm.network.enqueueWork import ru.dbotthepony.mc.otm.network.packetHandled import ru.dbotthepony.mc.otm.network.sender @@ -461,6 +462,16 @@ abstract class MatteryMenu @JvmOverloads protected constructor( return player.distanceToSqr(pos.x.toDouble() + 0.5, pos.y.toDouble() + 0.5, pos.z.toDouble() + 0.5) <= 64.0 } + fun syncCarried() { + setRemoteCarried(carried.copy()) + MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(carried)) + } + + fun syncCarried(stack: ItemStack) { + carried = stack + syncCarried() + } + private val externalSlots = ConditionalSet() private val quickMoveMapping = Reference2ObjectOpenHashMap>>() @@ -655,9 +666,9 @@ abstract class MatteryMenu @JvmOverloads protected constructor( // first pass - stack with existing slots if (copy.isStackable) { for (slot in slots) { - if (onlyFiltered && (slot !is UserFilteredSlot || slot.filter == null || slot.filter!!.get() != item.item)) { + if (onlyFiltered && (slot !is UserFilteredSlot || !slot.test(item))) { continue - } else if (!onlyFiltered && slot is UserFilteredSlot && slot.filter != null && slot.filter!!.get() != null && slot.filter!!.get() != item.item) { + } else if (!onlyFiltered && slot is UserFilteredSlot && !slot.test(item)) { continue } 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 5ba1b1ccf..2a4c59ed0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt @@ -15,6 +15,7 @@ import ru.dbotthepony.mc.otm.container.UpgradeContainer import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.runOnClient +import java.util.function.Predicate /** * Make slots for single container @@ -46,7 +47,7 @@ open class MatterySlot(container: Container, index: Int, x: Int = 0, y: Int = 0) } } -open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { +open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y), Predicate { var hasSetFilter = false private set @@ -60,6 +61,10 @@ open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int 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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt index ec1205e43..08aeb6c19 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.menu.data import com.mojang.blaze3d.platform.InputConstants import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import net.minecraft.client.Minecraft import net.minecraft.client.gui.screens.Screen import net.minecraft.network.FriendlyByteBuf @@ -13,21 +14,28 @@ import net.minecraft.world.item.ItemStack import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.PacketDistributor import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.core.addSorted +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.map import ru.dbotthepony.mc.otm.core.readBigInteger import ru.dbotthepony.mc.otm.core.writeBigInteger import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.network.* -import ru.dbotthepony.mc.otm.core.registryName +import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter import ru.dbotthepony.mc.otm.storage.* import java.math.BigInteger import java.util.* import java.util.function.Supplier +import kotlin.collections.ArrayList interface INetworkedItemViewProvider { val networkedItemView: NetworkedItemView } -class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: ClickAction) : MatteryPacket { +private fun all(stack: ItemStack): Int = stack.maxStackSize.coerceAtMost(stack.count) +private fun half(stack: ItemStack): Int = (stack.maxStackSize.coerceAtMost(stack.count) / 2).coerceAtLeast(1) + +data class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: ClickAction) : MatteryPacket { override fun write(buff: FriendlyByteBuf) { buff.writeInt(stackID) buff.writeEnum(type) @@ -53,144 +61,88 @@ class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: } } -object ClearItemViewPacket : MatteryPacket { - override fun write(buff: FriendlyByteBuf) { - // NO-OP - } - - override fun play(context: Supplier) { +abstract class NetworkedItemViewPacket : MatteryPacket { + final override fun play(context: Supplier) { context.packetHandled = true context.enqueueWork { - (minecraft.player?.containerMenu as? INetworkedItemViewProvider)?.networkedItemView?.clear() + val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork + val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No NetworkedItemView is present in currently open menu") + action(view) } } - fun read(buff: FriendlyByteBuf): ClearItemViewPacket { - return ClearItemViewPacket - } + protected abstract fun action(view: NetworkedItemView) +} - fun send(ply: ServerPlayer) { - MenuNetworkChannel.send(ply, this) +object ClearItemViewPacket : NetworkedItemViewPacket() { + override fun write(buff: FriendlyByteBuf) {} + + override fun action(view: NetworkedItemView) { + view.clear() } } -class StackAddPacket(val containerId: Int, val id: Int, val stack: ItemStorageStack) : MatteryPacket { +class StackAddPacket(val stackId: Int, val stack: ItemStorageStack) : NetworkedItemViewPacket() { override fun write(buff: FriendlyByteBuf) { - buff.writeInt(containerId) - buff.writeInt(id) + buff.writeInt(stackId) stack.write(buff) } - override fun play(context: Supplier) { - context.get().packetHandled = true - context.get().enqueueWork { - val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork - - if (get.containerId != containerId) - return@enqueueWork - - val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $containerId") - - if (view.localState.containsKey(id)) { - throw IllegalStateException("Item tracker $containerId already has stack with id of $id") - } - - val state = NetworkedItemView.NetworkedItem(id, stack) - view.localState[id] = state - - /*val iterator = view.sortedView.iterator().withIndex() - var lastCompare = 0 - var hit = false - - while (iterator.hasNext()) { - val (i, existing) = iterator.next() - val cmp = view.sorter.compare(existing.stack, stack) - - if (cmp != lastCompare && lastCompare != 0) { - hit = true - view.sortedView.add(i, state) - } else if (cmp != lastCompare) { - lastCompare = cmp - } - } - - if (!hit) {*/ - view.sortedView.add(state) - //} - - view.resort() + override fun action(view: NetworkedItemView) { + if (view.id2tuple.containsKey(stackId)) { + throw IllegalStateException("NetworkedItemView $view already contains stack with id $stackId") } + + val tuple = NetworkedItemView.Tuple(stackId, stack) + view.id2tuple[stackId] = tuple + view.sortedView.addSorted(tuple, view.sorter.map(NetworkedItemView.Tuple::stack)) } companion object { fun read(buffer: FriendlyByteBuf): StackAddPacket { - val containerId = buffer.readInt() val id = buffer.readInt() val item = StorageStack.ITEMS.read(buffer) - return StackAddPacket(containerId, id, item) + return StackAddPacket(id, item) } } } -class StackChangePacket(val id: Int, val stackID: Int, val newCount: BigInteger) : MatteryPacket { +class StackChangePacket(val stackId: Int, val newCount: BigInteger) : NetworkedItemViewPacket() { override fun write(buff: FriendlyByteBuf) { - buff.writeInt(id) - buff.writeInt(stackID) + buff.writeInt(stackId) buff.writeBigInteger(newCount) } - override fun play(context: Supplier) { - context.get().packetHandled = true - context.get().enqueueWork { - val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork - - if (get.containerId != id) - return@enqueueWork - - val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $id") - val state = view.localState[stackID] ?: throw IllegalStateException("No such stack with id $stackID in $view") - - state.stack = state.stack.copy(newCount) - view.resort() - } + override fun action(view: NetworkedItemView) { + val tuple = view.id2tuple[stackId] ?: throw IllegalStateException("No such stack with id $stackId in $view") + tuple.stack = tuple.stack.copy(newCount) + view.resort() } companion object { fun read(buffer: FriendlyByteBuf): StackChangePacket { - val id = buffer.readInt() val stackID = buffer.readInt() val newCount = buffer.readBigInteger() - return StackChangePacket(id, stackID, newCount) + return StackChangePacket(stackID, newCount) } } } -class StackRemovePacket(val id: Int, val stackID: Int) : MatteryPacket { +class StackRemovePacket(val stackId: Int) : NetworkedItemViewPacket() { override fun write(buff: FriendlyByteBuf) { - buff.writeInt(id) - buff.writeInt(stackID) + buff.writeInt(stackId) } - override fun play(context: Supplier) { - context.get().packetHandled = true - context.get().enqueueWork { - val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork - - if (get.containerId != id) - return@enqueueWork - - val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $id") - val obj = view.localState.remove(stackID) ?: throw IllegalStateException("No such stack with id $stackID in $view") - view.sortedView.remove(obj) - view.resort() - } + override fun action(view: NetworkedItemView) { + val obj = view.id2tuple.remove(stackId) ?: throw IllegalStateException("No such stack with id $stackId in $view") + view.sortedView.remove(obj) + view.resort() } companion object { fun read(buffer: FriendlyByteBuf): StackRemovePacket { - val id = buffer.readInt() val stackID = buffer.readInt() - return StackRemovePacket(id, stackID) + return StackRemovePacket(stackID) } } } @@ -198,129 +150,105 @@ class StackRemovePacket(val id: Int, val stackID: Int) : MatteryPacket { /** * Creates a virtual, slotless container for Player to interaction with. */ -open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: Boolean) : IStorageEventConsumer { - data class NetworkedItem(val id: Int, var stack: ItemStorageStack, val upstreamId: UUID? = null) +class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val isRemote: Boolean) : IStorageEventConsumer { + data class Tuple(val networkId: Int, var stack: ItemStorageStack, val upstreamId: UUID? = null) : Supplier { + override fun get(): ItemStorageStack { + return stack + } + } override val storageType: StorageStack.Type get() = StorageStack.ITEMS - // this (how client see and interact with) - val localState = Int2ObjectOpenHashMap() - - val sortedView = LinkedList() - var sorter: Comparator = NAME_SORTER - - companion object { - val NAME_SORTER = Comparator { o1, o2 -> - val cmp = o1.displayName.string.compareTo(o2.displayName.string) - - if (cmp != 0) - return@Comparator cmp - - return@Comparator o1.item.registryName.toString().compareTo(o2.item.registryName.toString()) + val id2tuple = Int2ObjectOpenHashMap() + val sortedView = ArrayList() + var sorter: Comparator = ItemStorageStackSorter.DEFAULT + set(value) { + if (field != value) { + field = value + resort() + } } - val COUNT_SORTER = Comparator { o1, o2 -> - val cmp = o1.count.compareTo(o2.count) + val itemCount get() = id2tuple.size - if (cmp != 0) - return@Comparator cmp + private var nextItemID = 0 + private val uuid2tuple = Object2ObjectOpenHashMap() + private val networkBacklog = ArrayList() - return@Comparator o1.item.registryName.toString().compareTo(o2.item.registryName.toString()) - } - } + operator fun get(id: Int): Tuple? = id2tuple[id] fun resort() { - sortedView.sortWith { a, b -> - return@sortWith sorter.compare(a.stack.toItemStack(), b.stack.toItemStack()) + if (isRemote) { + sortedView.sortWith(sorter.map(Tuple::stack)) } } - // parent (e.g. VirtualComponent) - protected val upstreamState = HashMap() - protected val networkBacklog = ArrayList() + var component: IStorageComponent? = null + set(provider) { + if (provider === field) return - operator fun get(id: Int): NetworkedItem? = localState[id] - - var provider: IStorageComponent? = null - private set + field?.removeListenerAndNotify(this) + field = provider + provider?.addListenerAndNotify(this) + } fun mouseClick(index: Int, mouseButton: Int) { - if (minecraft.player?.isSpectator == true) { - return - } + if (minecraft.player?.isSpectator == true) return - val list = sortedView - - val action = + MenuNetworkChannel.sendToServer(ItemViewInteractPacket( + sortedView.getOrNull(index)?.networkId ?: -1, + if (mouseButton == InputConstants.MOUSE_BUTTON_MIDDLE) ClickType.CLONE else if (Screen.hasShiftDown()) ClickType.QUICK_MOVE else ClickType.PICKUP, if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) ClickAction.PRIMARY else ClickAction.SECONDARY - - val type = - if (mouseButton == InputConstants.MOUSE_BUTTON_MIDDLE) ClickType.CLONE else if (Screen.hasShiftDown()) ClickType.QUICK_MOVE else ClickType.PICKUP - - MenuNetworkChannel.sendToServer( - ItemViewInteractPacket(if (index >= list.size) -1 else list[index].id, type, action) - ) - } - - fun setComponent(provider: IStorageComponent?) { - if (provider === this.provider) return - - this.provider?.removeListenerAndNotify(this) - this.provider = provider - provider?.addListenerAndNotify(this) + )) } fun removed() { - provider?.removeListenerAndNotify(this) + component?.removeListenerAndNotify(this) } - val itemCount get() = localState.values.size - override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider) { - check(!upstreamState.containsKey(id)) { "Already tracking ItemStack with upstream id $id!" } + check(!uuid2tuple.containsKey(id)) { "Already tracking ItemStack with upstream id $id!" } - val state = NetworkedItem(nextItemID++, stack, id) + val state = Tuple(nextItemID++, stack, id) - this.localState[state.id] = state - upstreamState[id] = state - network { StackAddPacket(menu.containerId, state.id, state.stack) } + this.id2tuple[state.networkId] = state + uuid2tuple[id] = state + network { StackAddPacket(state.networkId, state.stack) } } override fun onStackChanged(stack: ItemStorageStack, id: UUID) { - val get = upstreamState[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") + val get = uuid2tuple[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") get.stack = stack - network { StackChangePacket(menu.containerId, get.id, stack.count) } + network { StackChangePacket(get.networkId, stack.count) } } - protected fun network(fn: () -> Any) { - if (!remote) { + override fun onStackRemoved(id: UUID) { + val get = uuid2tuple[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") + uuid2tuple.remove(id) + id2tuple.remove(get.networkId) + network { StackRemovePacket(get.networkId) } + } + + private inline fun network(fn: () -> Any) { + if (!isRemote) { networkBacklog.add(fn()) } } - override fun onStackRemoved(id: UUID) { - val get = upstreamState[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") - upstreamState.remove(id) - localState.remove(get.id) - network { StackRemovePacket(menu.containerId, get.id) } - } - - protected var nextItemID = 0 - fun clear() { sortedView.clear() - upstreamState.clear() - localState.clear() + uuid2tuple.clear() + id2tuple.clear() - if (!remote) { + if (!isRemote) { networkBacklog.clear() networkBacklog.add(ClearItemViewPacket) } } fun network() { - check(!remote) { "Not a server" } + check(!isRemote) { "Not a server" } val consumer = PacketDistributor.PLAYER.with { ply as ServerPlayer } for (packet in networkBacklog) { @@ -331,82 +259,41 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: } fun playerInteract(packet: ItemViewInteractPacket) { - val provider = provider ?: return - - val click = packet.type - val action = packet.action - val stackId = packet.stackID - - if (click == ClickType.CLONE) { - if (stackId < 0 || !ply.abilities.instabuild) return - - val state = get(stackId) ?: return - val copy = state.stack.toItemStack().also { it.count = it.maxStackSize } - - ply.containerMenu.carried = copy - MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(ply.containerMenu.carried)) - ply.containerMenu.setRemoteCarried(ply.containerMenu.carried.copy()) + val component = component ?: return + val (stackId, type, action) = packet + if (type == ClickType.CLONE) { + if (!ply.abilities.instabuild) return + menu.syncCarried((get(stackId) ?: return).stack.toItemStack().also { it.count = it.maxStackSize }) return } - if (click == ClickType.QUICK_MOVE && stackId > -1) { - val state = get(stackId) ?: return - val stack = state.stack.toItemStack() + // забираем из системы с зажатым shift + if (type == ClickType.QUICK_MOVE) { + val tuple = get(stackId) ?: return + val stack = tuple.stack.toItemStack() - val amount = - if (action == ClickAction.PRIMARY) - stack.maxStackSize - else - 1.coerceAtLeast(stack.maxStackSize / 2) - - val extracted = provider.extractStack(state.upstreamId!!, amount.toBigInteger(), true) - - if (!extracted.isEmpty) { - val remaining = menu.quickMoveToInventory(extracted.toItemStack(), false) - - if (remaining.count != extracted.count.toInt()) { - provider.extractStack(state.upstreamId, (extracted.count.toInt() - remaining.count).toBigInteger(), false) - } - } + var amount = if (action == ClickAction.PRIMARY) all(stack) else half(stack) + amount -= menu.quickMoveToInventory(tuple.stack.toItemStack(amount), true).count + if (amount == 0) return + menu.quickMoveToInventory(component.extractStack(tuple.upstreamId!!, amount.toBigInteger(), false).toItemStack(), false) return } - if (!menu.carried.isEmpty && click != ClickType.QUICK_MOVE) { - // try to put + if (menu.carried.isNotEmpty) { if (action == ClickAction.PRIMARY) { - val carried = menu.carried - val amount = carried.count - - if (amount == carried.count) { - menu.carried = provider.insertStack(ItemStorageStack(menu.carried), false).toItemStack() - MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(menu.carried)) - menu.setRemoteCarried(menu.carried.copy()) - } - } else { - val copy = menu.carried.copy() - copy.count = 1 - - if (provider.insertStack(ItemStorageStack(copy), false).isEmpty) { - menu.carried.shrink(1) - MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(menu.carried)) - menu.setRemoteCarried(menu.carried.copy()) - } + menu.syncCarried(component.insertStack(ItemStorageStack(menu.carried), false).toItemStack()) + } else if (component.insertStack(ItemStorageStack(menu.carried.copyWithCount(1)), false).isEmpty) { + menu.carried.shrink(1) + menu.syncCarried() } } else if (stackId > -1) { val state = get(stackId) ?: return val stack = state.stack.toItemStack() - - val amount = - if (action == ClickAction.PRIMARY) - stack.maxStackSize - else - (stack.count / 2).coerceAtMost(stack.maxStackSize / 2).coerceAtLeast(1) - - menu.carried = provider.extractStack(state.upstreamId!!, amount.toBigInteger(), false).toItemStack() - MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(menu.carried)) - menu.setRemoteCarried(menu.carried.copy()) + val amount = if (action == ClickAction.PRIMARY) all(stack) else half(stack) + menu.carried = component.extractStack(state.upstreamId!!, amount.toBigInteger(), false).toItemStack() + menu.syncCarried() } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/AbstractPlayerInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/AbstractPlayerInputWithFeedback.kt index 8a4893d75..12480f58e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/AbstractPlayerInputWithFeedback.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/AbstractPlayerInputWithFeedback.kt @@ -3,7 +3,10 @@ package ru.dbotthepony.mc.otm.menu.input import net.minecraft.world.entity.player.Player import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.ISubscriptable +import ru.dbotthepony.mc.otm.core.SentientGetterSetter import ru.dbotthepony.mc.otm.menu.MatteryMenu +import ru.dbotthepony.mc.otm.network.synchronizer.IField import java.util.function.Consumer import java.util.function.Predicate import java.util.function.Supplier @@ -14,33 +17,7 @@ import kotlin.reflect.KMutableProperty0 * * Getting and setting values should ONLY be done clientside */ -interface IPlayerInputWithFeedback : GetterSetter, Predicate { - companion object { - fun of(getterSetter: GetterSetter): IPlayerInputWithFeedback { - return object : IPlayerInputWithFeedback, GetterSetter by getterSetter { - override fun test(t: Player?): Boolean { - return true - } - } - } - - fun of(getterSetter: GetterSetter, filter: Predicate): IPlayerInputWithFeedback { - return object : IPlayerInputWithFeedback, GetterSetter by getterSetter, Predicate by filter {} - } - - fun validPlayer(getterSetter: GetterSetter): IPlayerInputWithFeedback { - return object : IPlayerInputWithFeedback, GetterSetter by getterSetter { - override fun test(t: Player?): Boolean { - return t != null - } - } - } - } -} - -fun GetterSetter.wrapAsPlayerInput(filter: Predicate = Predicate { it != null }): IPlayerInputWithFeedback { - return IPlayerInputWithFeedback.of(this, filter) -} +interface IPlayerInputWithFeedback : SentientGetterSetter, Predicate /** * Represents Server to Client synchronization and Client to Server input @@ -49,13 +26,19 @@ fun GetterSetter.wrapAsPlayerInput(filter: Predicate = Predicate */ abstract class AbstractPlayerInputWithFeedback : IPlayerInputWithFeedback { abstract val input: MatteryMenu.PlayerInput - abstract val value: V + abstract val field: IField - override fun get(): V { + final override fun addListener(listener: Consumer): ISubscriptable.L { + return field.addListener(listener) + } + + val value: V get() = this.field.value + + final override fun get(): V { return value } - override fun accept(t: V) { + final override fun accept(t: V) { input.input(t) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/BooleanInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/BooleanInputWithFeedback.kt index 21d71fca9..b90ab2934 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/BooleanInputWithFeedback.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/BooleanInputWithFeedback.kt @@ -5,16 +5,28 @@ import ru.dbotthepony.mc.otm.menu.MatteryMenu import java.util.function.BooleanSupplier import kotlin.reflect.KMutableProperty0 -class BooleanInputWithFeedback(menu: MatteryMenu) : AbstractPlayerInputWithFeedback() { - override val input = menu.booleanInput { consumer?.invoke(it) } - override val value by menu.mSynchronizer.computedBool(BooleanSupplier { supplier?.invoke() ?: false }).property +class BooleanInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean = false) : AbstractPlayerInputWithFeedback() { + override val input = menu.booleanInput(allowSpectators) { consumer?.invoke(it) } + override val field = menu.mSynchronizer.computedBool(BooleanSupplier { supplier?.invoke() ?: false }) - constructor(menu: MatteryMenu, state: KMutableProperty0) : this(menu) { - with(state) + constructor(menu: MatteryMenu, allowSpectators: Boolean, state: KMutableProperty0?) : this(menu, allowSpectators) { + if (state != null) + with(state) } - constructor(menu: MatteryMenu, state: GetterSetter) : this(menu) { - with(state) + constructor(menu: MatteryMenu, allowSpectators: Boolean, state: GetterSetter?) : this(menu, allowSpectators) { + if (state != null) + with(state) + } + + constructor(menu: MatteryMenu, state: KMutableProperty0?) : this(menu) { + if (state != null) + with(state) + } + + constructor(menu: MatteryMenu, state: GetterSetter?) : this(menu) { + if (state != null) + with(state) } fun switchValue() { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt index 61df43a35..4c5877a1a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt @@ -9,12 +9,16 @@ inline fun > EnumInputWithFeedback(menu: MatteryMenu) = Enum inline fun > EnumInputWithFeedback(menu: MatteryMenu, state: KMutableProperty0?) = EnumInputWithFeedback(menu, E::class.java, state) inline fun > EnumInputWithFeedback(menu: MatteryMenu, state: GetterSetter) = EnumInputWithFeedback(menu, E::class.java, state) -class EnumInputWithFeedback>(menu: MatteryMenu, clazz: Class) : AbstractPlayerInputWithFeedback() { +inline fun > EnumInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean) = EnumInputWithFeedback(menu, E::class.java, allowSpectators) +inline fun > EnumInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean, state: KMutableProperty0?) = EnumInputWithFeedback(menu, E::class.java, allowSpectators, state) +inline fun > EnumInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean, state: GetterSetter) = EnumInputWithFeedback(menu, E::class.java, allowSpectators, state) + +class EnumInputWithFeedback>(menu: MatteryMenu, clazz: Class, allowSpectators: Boolean = false) : AbstractPlayerInputWithFeedback() { val codec = EnumValueCodec(clazz) private val default = codec.values.first() - override val input = menu.PlayerInput(codec, false) { consumer?.invoke(it) } - override val value by menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec) + override val input = menu.PlayerInput(codec, allowSpectators) { consumer?.invoke(it) } + override val field = menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec) constructor(menu: MatteryMenu, clazz: Class, state: KMutableProperty0?) : this(menu, clazz) { if (state != null) { @@ -22,7 +26,21 @@ class EnumInputWithFeedback>(menu: MatteryMenu, clazz: Class) : A } } - constructor(menu: MatteryMenu, clazz: Class, state: GetterSetter) : this(menu, clazz) { - with(state) + constructor(menu: MatteryMenu, clazz: Class, state: GetterSetter?) : this(menu, clazz) { + if (state != null) { + with(state) + } + } + + constructor(menu: MatteryMenu, clazz: Class, allowSpectators: Boolean, state: KMutableProperty0?) : this(menu, clazz, allowSpectators) { + if (state != null) { + with(state) + } + } + + constructor(menu: MatteryMenu, clazz: Class, allowSpectators: Boolean, state: GetterSetter?) : this(menu, clazz, allowSpectators) { + if (state != null) { + with(state) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/StringInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/StringInputWithFeedback.kt index 9d918dd2d..617bd9dfc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/StringInputWithFeedback.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/StringInputWithFeedback.kt @@ -6,7 +6,7 @@ import kotlin.reflect.KMutableProperty0 class StringInputWithFeedback(menu: MatteryMenu) : AbstractPlayerInputWithFeedback() { override val input = menu.stringInput { consumer?.invoke(it.replace('\u0000', ' ')) } - override val value by menu.mSynchronizer.computedString { supplier?.invoke() ?: "" } + override val field = menu.mSynchronizer.computedString { supplier?.invoke() ?: "" } constructor(menu: MatteryMenu, state: KMutableProperty0) : this(menu) { with(state) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt index fb69a94a1..67fdf18e4 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt @@ -190,8 +190,8 @@ class MatterPanelMenu( val sortingGS = GetterSetter.of(::sorting, changeSorting::input) val isAscendingGS = GetterSetter.of(::isAscending, changeIsAscending::input) - private val actualComparator = Comparator { o1, o2 -> sorting.comparator.compare(o1.item, o2.item) * (if (isAscending) 1 else -1) } - private val actualTaskComparator = Comparator { o1, o2 -> sorting.comparator.compare(o1.item, o2.item) * (if (isAscending) 1 else -1) } + private val actualComparator = Comparator { o1, o2 -> sorting.compare(o1.item, o2.item) * (if (isAscending) 1 else -1) } + private val actualTaskComparator = Comparator { o1, o2 -> sorting.compare(o1.item, o2.item) * (if (isAscending) 1 else -1) } private val patterns = ArrayList() private val tasks = ArrayList() 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 89e4e664c..ebad3143d 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,57 +1,67 @@ package ru.dbotthepony.mc.otm.menu.storage -import com.google.common.collect.ImmutableList import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player -import net.minecraft.world.inventory.Slot import net.minecraft.world.item.ItemStack import net.minecraftforge.common.capabilities.ForgeCapabilities 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.ItemFilter import ru.dbotthepony.mc.otm.core.ifPresentK +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.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.widget.ProfiledLevelGaugeWidget import ru.dbotthepony.mc.otm.registry.MMenus import ru.dbotthepony.mc.otm.storage.ItemStorageStack import ru.dbotthepony.mc.otm.storage.StorageStack import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent -class DriveViewerMenu @JvmOverloads constructor( +class DriveViewerMenu( containerID: Int, inventory: Inventory, tile: DriveViewerBlockEntity? = null -) : MatteryPoweredMenu( - MMenus.DRIVE_VIEWER, containerID, inventory, tile -), INetworkedItemViewProvider { +) : MatteryPoweredMenu(MMenus.DRIVE_VIEWER, containerID, inventory, tile), INetworkedItemViewProvider { override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null) - val driveSlot: MatterySlot + + val driveSlot = object : MatterySlot(tile?.container ?: SimpleContainer(1), 0) { + override fun mayPlace(itemStack: ItemStack): Boolean { + return itemStack.getCapability(MatteryCapability.DRIVE).isPresent + } + } private val powered: PoweredVirtualComponent? private var lastDrive: IMatteryDrive? = null + val profiledEnergy = ProfiledLevelGaugeWidget>(this, energyWidget) + val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig) - init { - val container = tile?.container ?: SimpleContainer(1) + val settings = object : DriveViewerBlockEntity.ISettings { + override var sorting: ItemStorageStackSorter by EnumInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(ply)::sorting }).also { it.addListener { changes() } } + override var isAscending: Boolean by BooleanInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(ply)::isAscending }).also { it.addListener { changes() } } - driveSlot = object : MatterySlot(container, 0) { - override fun mayPlace(itemStack: ItemStack): Boolean { - return itemStack.getCapability(MatteryCapability.DRIVE).isPresent + private fun changes() { + if (isAscending) { + networkedItemView.sorter = sorting + } else { + networkedItemView.sorter = sorting.reversed } } + } + init { if (tile != null) { - powered = PoweredVirtualComponent( - StorageStack.ITEMS, - tile.getCapability(MatteryCapability.ENERGY).resolve()::get - ) - - this.networkedItemView.setComponent(powered) + profiledEnergy.with(tile.energy) + powered = PoweredVirtualComponent(StorageStack.ITEMS, tile::energy) + this.networkedItemView.component = powered } else { powered = null } @@ -126,12 +136,9 @@ class DriveViewerMenu @JvmOverloads constructor( } } - override fun removed(p_38940_: Player) { - super.removed(p_38940_) - - if (lastDrive != null) - powered?.remove(lastDrive!!) - + override fun removed(player: Player) { + super.removed(player) + lastDrive?.let { powered?.remove(it) } this.networkedItemView.removed() } 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 d7714883c..373f7390e 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 @@ -6,15 +6,20 @@ 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.minecraftforge.network.PacketDistributor +import ru.dbotthepony.mc.otm.block.entity.storage.IItemMonitorPlayerSettings import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings import ru.dbotthepony.mc.otm.container.get +import ru.dbotthepony.mc.otm.core.collect.mapToInt +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.data.INetworkedItemViewProvider import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView -import ru.dbotthepony.mc.otm.network.MenuNetworkChannel +import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback +import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback +import ru.dbotthepony.mc.otm.menu.makeSlots import ru.dbotthepony.mc.otm.registry.MMenus import ru.dbotthepony.mc.otm.storage.* import java.util.* @@ -73,73 +78,85 @@ private class ResultSlot(container: Container) : MatterySlot(container, 0) { } } -class ItemMonitorMenu @JvmOverloads constructor( +class ItemMonitorMenu( containerId: Int, inventory: Inventory, tile: ItemMonitorBlockEntity? = null ) : MatteryPoweredMenu(MMenus.ITEM_MONITOR, containerId, inventory, tile), INetworkedItemViewProvider { override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null) - val settings: ItemMonitorPlayerSettings = tile?.getSettings(inventory.player as ServerPlayer) ?: ItemMonitorPlayerSettings() - private var settingsNetworked = false + + val settings = object : IItemMonitorPlayerSettings { + override var ingredientPriority by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::ingredientPriority }) + override var resultTarget by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::resultTarget }) + override var craftingAmount by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::craftingAmount }) + override var sorting by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::sorting }).also { it.addListener { changes() } } + override var ascendingSort by BooleanInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::ascendingSort }).also { it.addListener { changes() } } + + private fun changes() { + if (ascendingSort) { + networkedItemView.sorter = sorting + } else { + networkedItemView.sorter = sorting.reversed + } + } + } val craftingResult: MatterySlot val craftingSlots: List init { if (tile != null) { - networkedItemView.setComponent(tile.poweredView) - craftingResult = ResultSlot(tile.craftingResultContainer) - craftingSlots = Collections.unmodifiableList(Array(9) { MatterySlot(tile.craftingGrid, it) }.asList()) - } else { - craftingResult = ResultSlot(SimpleContainer(1)) - val container = SimpleContainer(9) - craftingSlots = Collections.unmodifiableList(Array(9) { MatterySlot(container, it) }.asList()) + networkedItemView.component = tile.poweredView } - val slots = craftingSlots.map(this::addSlot) + craftingResult = ResultSlot(tile?.craftingResultContainer ?: SimpleContainer(1)) + craftingSlots = makeSlots(tile?.craftingGrid ?: SimpleContainer(9), ::MatterySlot) + craftingSlots.forEach(this::addSlot) addSlot(craftingResult) addInventorySlots() } - override fun broadcastFullState() { - super.broadcastFullState() - MenuNetworkChannel.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings) - settingsNetworked = true - } - - fun sendSettingsToServer() { - MenuNetworkChannel.sendToServer(settings) - } - - override fun removed(p_38940_: Player) { - super.removed(p_38940_) + override fun removed(player: Player) { + super.removed(player) networkedItemView.removed() } override fun broadcastChanges() { super.broadcastChanges() networkedItemView.network() + } - if (!settingsNetworked) { - MenuNetworkChannel.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings) - settingsNetworked = true + private fun moveCrafting(view: IStorageComponent, simulate: Boolean): Boolean { + val itemStack = craftingResult.item + var remaining: ItemStack + + if (settings.resultTarget == ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM) { + remaining = view.insertStack(ItemStorageStack(itemStack), simulate).toItemStack() + remaining = moveItemStackToSlots(remaining, playerInventorySlots, simulate) + } else { + remaining = moveItemStackToSlots(itemStack, playerInventorySlots, simulate) + remaining = view.insertStack(ItemStorageStack(remaining), simulate).toItemStack() } + + if (!simulate && remaining.isNotEmpty) { + ply.spawnAtLocation(remaining) + } + + if (!simulate) { + craftingResult.remove(itemStack.count) + } + + return remaining.count != itemStack.count } override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack { if (playerInventorySlots.any { it.index == slotIndex }) { - if (tile == null) { - return ItemStack.EMPTY - } - + if (tile == null) return ItemStack.EMPTY val slot = slots[slotIndex] + if (slot.item.isEmpty) return ItemStack.EMPTY - if (slot.item.isEmpty) { - return ItemStack.EMPTY - } - - val leftover = networkedItemView.provider?.insertStack(ItemStorageStack(slot.item), false)?.toItemStack() ?: slot.item + val leftover = networkedItemView.component?.insertStack(ItemStorageStack(slot.item), false)?.toItemStack() ?: slot.item if (leftover.count == slot.item.count) { return ItemStack.EMPTY @@ -148,77 +165,48 @@ class ItemMonitorMenu @JvmOverloads constructor( val old = slot.item.copy() slot.item.count = leftover.count return old - } else if (slotIndex in craftingSlots[0].index .. craftingSlots[craftingSlots.size - 1].index) { + } else if (craftingSlots.any { it.index == slotIndex }) { // from crafting grid to inventory val item = slots[slotIndex].item + if (item.isEmpty) return ItemStack.EMPTY - if (item.isEmpty) { - return ItemStack.EMPTY + var remainder = item + + when (settings.ingredientPriority) { + ItemMonitorPlayerSettings.IngredientPriority.SYSTEM, ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST -> { + remainder = networkedItemView.component?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder + } + + else -> {} } - var remainder = moveItemStackToSlots(item, playerInventorySlots) + remainder = moveItemStackToSlots(remainder, playerInventorySlots) + slots[slotIndex].set(remainder) - - if (remainder.isEmpty) { - return item - } - - remainder = networkedItemView.provider?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder - slots[slotIndex].set(remainder) - - if (remainder.isEmpty) { - return item - } - return if (remainder.count != item.count) item else ItemStack.EMPTY } else if (slotIndex == craftingResult.index) { - // quickcraft... god damn it - if (!craftingResult.hasItem()) { - return ItemStack.EMPTY - } - - val item = craftingResult.item - + var item = craftingResult.item + if (item.isEmpty) return ItemStack.EMPTY val tile = tile as ItemMonitorBlockEntity? ?: return ItemStack.EMPTY if (tile.lastCraftingRecipe(ply) != null && tile.craftingRecipe != tile.lastCraftingRecipe(ply)) { - // recipe has changed + // рецепт изменился return ItemStack.EMPTY } + val crafted = tile.howMuchPlayerCrafted(ply) + if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.ONE) { - if (tile.howMuchPlayerCrafted(ply) > 0) { + if (crafted > 0) { return ItemStack.EMPTY } } else { - var hasUnstackables = false - var maxStack = 64 - - if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.FULL) { - for (gridItem in tile.craftingGrid.iterator()) { - if (!gridItem.isStackable) { - hasUnstackables = true - break - } - } - } else { - maxStack = 0 - - for (gridItem in tile.craftingGrid.iterator()) { - maxStack = maxStack.coerceAtLeast(gridItem.maxStackSize) - } - } - - if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.STACK || hasUnstackables) { - val count = tile.howMuchPlayerCrafted(ply) - - if (count > 0 && (count + 1) * craftingResult.item.count > craftingResult.item.maxStackSize) { + if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.STACK || tile.craftingGrid.any { !it.isStackable }) { + if (crafted > 0 && (crafted + 1) * item.count > item.maxStackSize) { return ItemStack.EMPTY } } else { - val count = tile.howMuchPlayerCrafted(ply) - - if (count > 0 && count >= maxStack) { + if (crafted > 0 && crafted >= tile.craftingGrid.iterator().mapToInt { it.maxStackSize }.reduce(Int.MAX_VALUE, Int::coerceAtMost)) { return ItemStack.EMPTY } } @@ -227,50 +215,12 @@ class ItemMonitorMenu @JvmOverloads constructor( tile.craftingResultContainer.craftingPlayer = ply as ServerPlayer try { - when (settings.resultTarget) { - ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM -> { - val wrapper = ItemStorageStack(item) - var remaining = tile.poweredView?.insertStack(wrapper, true)?.toItemStack() ?: return ItemStack.EMPTY - - if (remaining.isEmpty) { - tile.poweredView!!.insertStack(wrapper, false) - craftingResult.remove(item.count) - return item - } - - remaining = moveItemStackToSlots(remaining, playerInventorySlots, simulate = true) - - if (remaining.isEmpty) { - remaining = tile.poweredView!!.insertStack(wrapper, false).toItemStack() - moveItemStackToSlots(remaining, playerInventorySlots, simulate = false) - craftingResult.remove(item.count) - return item - } - - return ItemStack.EMPTY - } - - ItemMonitorPlayerSettings.ResultTarget.MIXED, ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY -> { - var remaining = moveItemStackToSlots(item, playerInventorySlots, simulate = true) - - if (remaining.isEmpty) { - moveItemStackToSlots(item, playerInventorySlots, simulate = false) - craftingResult.remove(item.count) - return item - } - - val wrapper = ItemStorageStack(remaining) - remaining = tile.poweredView?.insertStack(wrapper, true)?.toItemStack() ?: return ItemStack.EMPTY - - if (remaining.isEmpty) { - moveItemStackToSlots(item, playerInventorySlots, simulate = false) - tile.poweredView!!.insertStack(wrapper, false) - craftingResult.remove(item.count) - return item - } - - return ItemStack.EMPTY - } + if (moveCrafting(tile.poweredView ?: return ItemStack.EMPTY, true)) { + item = item.copy() + moveCrafting(tile.poweredView ?: return ItemStack.EMPTY, false) + return item + } else { + return ItemStack.EMPTY } } finally { tile.craftingResultContainer.craftingPlayer = null diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt index 84a72b312..829d0fc6d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/MenuNetworkChannel.kt @@ -81,8 +81,8 @@ object MenuNetworkChannel : MatteryNetworkChannel( add(SetCarriedPacket::class.java, SetCarriedPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(ItemFilterSlotPacket::class.java, ItemFilterSlotPacket.Companion::read) - // networked view - add(ClearItemViewPacket::class.java, ClearItemViewPacket::read, NetworkDirection.PLAY_TO_CLIENT) + // networked item view + add(ClearItemViewPacket::class.java, { ClearItemViewPacket }, NetworkDirection.PLAY_TO_CLIENT) add(ItemViewInteractPacket::class.java, ItemViewInteractPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) add(StackAddPacket::class.java, StackAddPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(StackChangePacket::class.java, StackChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) @@ -94,9 +94,6 @@ object MenuNetworkChannel : MatteryNetworkChannel( // Client->Server add(MatteryMenu.PlayerInputPacket::class.java, MatteryMenu::PlayerInputPacket, NetworkDirection.PLAY_TO_SERVER) - // Item monitor - add(ItemMonitorPlayerSettings::class.java, ItemMonitorPlayerSettings.Companion::read) - // matter panel menu add(CancelTaskPacket::class.java, CancelTaskPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) add(PatternsChangePacket::class.java, PatternsChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSynchronizer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSynchronizer.kt index 4df86bf24..523b28c8b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSynchronizer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/FieldSynchronizer.kt @@ -33,6 +33,7 @@ import java.lang.ref.WeakReference import java.math.BigDecimal import java.util.* import java.util.function.BooleanSupplier +import java.util.function.Consumer import java.util.function.DoubleConsumer import java.util.function.DoubleSupplier import java.util.function.IntConsumer @@ -529,6 +530,11 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa isObserver: Boolean = false, ) : AbstractField(), IMutableField { private var remote: V = codec.copy(field) + private val subs = ISubscriptable.Impl() + + override fun addListener(listener: Consumer): ISubscriptable.L { + return subs.addListener(listener) + } init { if (isObserver) { @@ -548,6 +554,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa } this@Field.field = value + subs.accept(value) } } @@ -577,15 +584,15 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa if (setter != null) { setter.invoke(value, access, false) - return - } + } else { + if (!isDirty && !codec.compare(remote, value)) { + notifyEndpoints(this@Field) + isDirty = true + } - if (!isDirty && !codec.compare(remote, value)) { - notifyEndpoints(this@Field) - isDirty = true + this.field = value + subs.accept(value) } - - this.field = value } override fun write(stream: DataOutputStream, endpoint: Endpoint) { @@ -602,10 +609,10 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa if (setter != null) { setter.invoke(value, access, true) - return + } else { + this.field = value + subs.accept(value) } - - this.field = value } } @@ -619,7 +626,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa /** * Type specific field, storing primitive [Float] directly */ - inner class FloatField(private var field: Float, private val getter: FloatFieldGetter? = null, private val setter: FloatFieldSetter? = null) : PrimitiveField(), IMutableFloatField { + inner class FloatField(field: Float, private val getter: FloatFieldGetter? = null, private val setter: FloatFieldSetter? = null) : PrimitiveField(), IMutableFloatField { + private val subs = IFloatSubcripable.Impl() + + override fun addListener(listener: FloatConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + private var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + override val property = object : IMutableFloatProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Float { return this@FloatField.float @@ -686,7 +707,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa /** * Type specific field, storing primitive [Double] directly */ - inner class DoubleField(private var field: Double, private val getter: DoubleFieldGetter? = null, private val setter: DoubleFieldSetter? = null) : PrimitiveField(), IMutableDoubleField { + inner class DoubleField(field: Double, private val getter: DoubleFieldGetter? = null, private val setter: DoubleFieldSetter? = null) : PrimitiveField(), IMutableDoubleField { + private val subs = IDoubleSubcripable.Impl() + + override fun addListener(listener: DoubleConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + private var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + override val property = object : IMutableDoubleProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Double { return this@DoubleField.double @@ -750,7 +785,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa } } - abstract inner class AbstractIntField(protected var field: Int, private val getter: IntFieldGetter? = null, protected val setter: IntFieldSetter? = null) : PrimitiveField(), IMutableIntField { + abstract inner class AbstractIntField(field: Int, private val getter: IntFieldGetter? = null, protected val setter: IntFieldSetter? = null) : PrimitiveField(), IMutableIntField { + private val subs = IIntSubcripable.Impl() + + override fun addListener(listener: IntConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + protected var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + final override val property = object : IMutableIntProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Int { return this@AbstractIntField.int @@ -845,7 +894,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa /** * Type specific field, storing primitive [Long] directly */ - abstract inner class AbstractLongField(protected var field: Long, private val getter: LongFieldGetter? = null, protected val setter: LongFieldSetter? = null) : PrimitiveField(), IMutableLongField { + abstract inner class AbstractLongField(field: Long, private val getter: LongFieldGetter? = null, protected val setter: LongFieldSetter? = null) : PrimitiveField(), IMutableLongField { + private val subs = ILongSubcripable.Impl() + + override fun addListener(listener: LongConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + protected var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + final override val property = object : IMutableLongProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Long { return this@AbstractLongField.long @@ -940,7 +1003,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa /** * Type specific field, storing primitive [Boolean] directly */ - inner class BooleanField(private var field: Boolean, private val getter: BooleanFieldGetter? = null, private val setter: BooleanFieldSetter? = null) : PrimitiveField(), IMutableBooleanField { + inner class BooleanField(field: Boolean, private val getter: BooleanFieldGetter? = null, private val setter: BooleanFieldSetter? = null) : PrimitiveField(), IMutableBooleanField { + private val subs = IBooleanSubscriptable.Impl() + + override fun addListener(listener: BooleanConsumer): ISubscriptable.L { + return subs.addListener(listener) + } + + private var field = field + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + override val property = object : IMutableBooleanProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { return this@BooleanField.boolean @@ -1014,6 +1091,11 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa ) : AbstractField(), IField { private var remote: Any? = Mark private var clientValue: Any? = Mark + private val subs = ISubscriptable.Impl() + + override fun addListener(listener: Consumer): ISubscriptable.L { + return subs.addListener(listener) + } init { observers.add(this) @@ -1055,6 +1137,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa val newValue = codec.read(stream) clientValue = newValue observer.invoke(newValue) + subs.accept(newValue) } } @@ -1063,11 +1146,22 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa * * This class has concrete implementation for [Float] primitive */ - inner class ComputedFloatField(private val getter: FloatSupplier, private val observer: FloatConsumer = FloatConsumer {}) : AbstractField(), IFloatField { + inner class ComputedFloatField(private val getter: FloatSupplier, observer: FloatConsumer? = null) : AbstractField(), IFloatField { private var remote: Float = 0f private var isRemoteSet = false private var clientValue: Float = 0f private var isClientValue = false + private val subs = IFloatSubcripable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: FloatConsumer): ISubscriptable.L { + return subs.addListener(listener) + } override val property = object : IFloatProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Float { @@ -1120,7 +1214,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa val newValue = stream.readFloat() clientValue = newValue isClientValue = true - observer.accept(newValue) + subs.accept(newValue) } @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) @@ -1134,11 +1228,22 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa * * This class has concrete implementation for [Double] primitive */ - inner class ComputedDoubleField(private val getter: DoubleSupplier, private val observer: DoubleConsumer = DoubleConsumer {}) : AbstractField(), IDoubleField { + inner class ComputedDoubleField(private val getter: DoubleSupplier, observer: DoubleConsumer? = null) : AbstractField(), IDoubleField { private var remote: Double = 0.0 private var isRemoteSet = false private var clientValue: Double = 0.0 private var isClientValue = false + private val subs = IDoubleSubcripable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: DoubleConsumer): ISubscriptable.L { + return subs.addListener(listener) + } override val property = object : IDoubleProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Double { @@ -1185,7 +1290,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa val newValue = stream.readDouble() clientValue = newValue isClientValue = true - observer.accept(newValue) + subs.accept(newValue) } @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) @@ -1199,11 +1304,29 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa * * This class has concrete implementation for [Int] primitive */ - abstract inner class AbstractComputedIntField(protected val getter: IntSupplier, protected val observer: IntConsumer = IntConsumer {}) : AbstractField(), IIntField { + abstract inner class AbstractComputedIntField(protected val getter: IntSupplier, observer: IntConsumer? = null) : AbstractField(), IIntField { private var remote: Int = 0 private var isRemoteSet = false protected var clientValue: Int = 0 + set(value) { + if (field != value) { + field = value + subs.accept(value) + } + } + protected var isClientValue = false + private val subs = IIntSubcripable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: IntConsumer): ISubscriptable.L { + return subs.addListener(listener) + } final override val property = object : IIntProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Int { @@ -1259,10 +1382,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa override fun read(stream: DataInputStream) { check(!isRemoved) { "Field was removed" } - val newValue = stream.readVarIntLE() - clientValue = newValue - isClientValue = true - observer.accept(newValue) + clientValue = stream.readVarIntLE() } } @@ -1280,10 +1400,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa override fun read(stream: DataInputStream) { check(!isRemoved) { "Field was removed" } - val newValue = stream.readInt() - clientValue = newValue - isClientValue = true - observer.accept(newValue) + clientValue = stream.readInt() } } @@ -1292,11 +1409,31 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa * * This class has concrete implementation for [Long] primitive */ - abstract inner class AbstractComputedLongField(protected val getter: LongSupplier, protected val observer: LongConsumer = LongConsumer {}) : AbstractField(), ILongField { + abstract inner class AbstractComputedLongField(protected val getter: LongSupplier, observer: LongConsumer? = null) : AbstractField(), ILongField { private var remote: Long = 0L private var isRemoteSet = false protected var clientValue: Long = 0L + set(value) { + isClientValue = true + + if (field != value) { + field = value + subs.accept(value) + } + } + protected var isClientValue = false + private val subs = ILongSubcripable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: LongConsumer): ISubscriptable.L { + return subs.addListener(listener) + } final override val property = object : ILongProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Long { @@ -1352,10 +1489,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa override fun read(stream: DataInputStream) { check(!isRemoved) { "Field was removed" } - val newValue = stream.readVarLongLE() - clientValue = newValue - isClientValue = true - observer.accept(newValue) + clientValue = stream.readVarLongLE() } } @@ -1373,10 +1507,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa override fun read(stream: DataInputStream) { check(!isRemoved) { "Field was removed" } - val newValue = stream.readLong() - clientValue = newValue - isClientValue = true - observer.accept(newValue) + clientValue = stream.readLong() } } @@ -1385,11 +1516,22 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa * * This class has concrete implementation for [Boolean] primitive */ - inner class ComputedBooleanField(private val getter: BooleanSupplier, private val observer: BooleanConsumer = BooleanConsumer {}) : AbstractField(), IBooleanField { + inner class ComputedBooleanField(private val getter: BooleanSupplier, observer: BooleanConsumer? = null) : AbstractField(), IBooleanField { private var remote: Boolean = false private var isRemoteSet = false private var clientValue: Boolean = false private var isClientValue = false + private val subs = IBooleanSubscriptable.Impl() + + init { + if (observer != null) { + subs.addListener(observer) + } + } + + override fun addListener(listener: BooleanConsumer): ISubscriptable.L { + return subs.addListener(listener) + } override val property = object : IBooleanProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { @@ -1436,7 +1578,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa val newValue = stream.readBoolean() clientValue = newValue isClientValue = true - observer.accept(newValue) + subs.accept(newValue) } @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) @@ -1453,6 +1595,11 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa private val getter: () -> V private val setter: (V) -> Unit private var remote: V + private val subs = ISubscriptable.Impl() + + override fun addListener(listener: Consumer): ISubscriptable.L { + return subs.addListener(listener) + } override var value: V get() = getter.invoke() @@ -1499,6 +1646,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa override fun read(stream: DataInputStream) { check(!isRemoved) { "Field was removed" } this.value = codec.read(stream) + subs.accept(this.value) } } @@ -1506,7 +1654,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa private val codec: IStreamCodec, private val backingSet: MutableSet, private val callback: ((changes: Collection>) -> Unit)? = null, - ) : AbstractField>() { + ) : AbstractField>(), ISubscriptable> by ISubscriptable.empty() { private var isRemote = false private fun pushBacklog(element: E, action: (DataOutputStream) -> Unit) { @@ -1745,7 +1893,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa private val valueCodec: IStreamCodec, private val backingMap: MutableMap, private val callback: ((changes: Collection>) -> Unit)? = null, - ) : AbstractField>(), IField> { + ) : AbstractField>(), IField>, ISubscriptable> by ISubscriptable.empty() { private var sentAllValues = false private var isRemote = false diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/Fields.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/Fields.kt index 3cbfeb41b..c3276c8cd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/Fields.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/Fields.kt @@ -1,6 +1,12 @@ package ru.dbotthepony.mc.otm.network.synchronizer import ru.dbotthepony.mc.otm.core.FloatSupplier +import ru.dbotthepony.mc.otm.core.IBooleanSubscriptable +import ru.dbotthepony.mc.otm.core.IDoubleSubcripable +import ru.dbotthepony.mc.otm.core.IFloatSubcripable +import ru.dbotthepony.mc.otm.core.IIntSubcripable +import ru.dbotthepony.mc.otm.core.ILongSubcripable +import ru.dbotthepony.mc.otm.core.ISubscriptable import java.io.DataInputStream import java.io.DataOutputStream import java.util.function.BooleanSupplier @@ -11,7 +17,7 @@ import java.util.function.Supplier import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty -sealed interface IField : ReadOnlyProperty, Supplier, () -> V { +sealed interface IField : ReadOnlyProperty, Supplier, () -> V, ISubscriptable { fun observe(): Boolean fun markDirty() fun markDirty(endpoint: FieldSynchronizer.Endpoint) @@ -40,7 +46,7 @@ interface IFloatProperty { operator fun getValue(thisRef: Any?, property: KProperty<*>): Float } -sealed interface IFloatField : IField, FloatSupplier { +sealed interface IFloatField : IField, FloatSupplier, IFloatSubcripable { val float: Float val property: IFloatProperty @@ -66,7 +72,7 @@ interface IDoubleProperty { operator fun getValue(thisRef: Any?, property: KProperty<*>): Double } -sealed interface IDoubleField : IField, DoubleSupplier { +sealed interface IDoubleField : IField, DoubleSupplier, IDoubleSubcripable { val double: Double val property: IDoubleProperty @@ -92,7 +98,7 @@ interface IIntProperty { operator fun getValue(thisRef: Any?, property: KProperty<*>): Int } -sealed interface IIntField : IField, IntSupplier { +sealed interface IIntField : IField, IntSupplier, IIntSubcripable { val int: Int val property: IIntProperty @@ -118,7 +124,7 @@ interface ILongProperty { operator fun getValue(thisRef: Any?, property: KProperty<*>): Long } -sealed interface ILongField : IField, LongSupplier { +sealed interface ILongField : IField, LongSupplier, ILongSubcripable { val long: Long val property: ILongProperty @@ -144,7 +150,7 @@ interface IBooleanProperty { operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean } -sealed interface IBooleanField : IField, BooleanSupplier { +sealed interface IBooleanField : IField, BooleanSupplier, IBooleanSubscriptable { val boolean: Boolean val property: IBooleanProperty diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MutableFields.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MutableFields.kt index a6937cf89..4f385ad01 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MutableFields.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/synchronizer/MutableFields.kt @@ -3,12 +3,13 @@ package ru.dbotthepony.mc.otm.network.synchronizer import it.unimi.dsi.fastutil.booleans.BooleanConsumer import it.unimi.dsi.fastutil.floats.FloatConsumer import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.SentientGetterSetter import java.util.function.DoubleConsumer import java.util.function.IntConsumer import java.util.function.LongConsumer import kotlin.reflect.KProperty -sealed interface IMutableField : IField, GetterSetter { +sealed interface IMutableField : IField, SentientGetterSetter { override fun getValue(thisRef: Any?, property: KProperty<*>): V { return this.value } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt index b31328a25..6f10f057f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt @@ -18,7 +18,7 @@ import net.minecraftforge.registries.ForgeRegistries import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.capability.matter.matter import ru.dbotthepony.mc.otm.capability.matteryEnergy -import ru.dbotthepony.mc.otm.core.util.CreativeMenuComparator +import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.ifPresentK import ru.dbotthepony.mc.otm.core.registryName @@ -177,6 +177,9 @@ private fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) { accept(MItems.PATTERN_DRIVE_CREATIVE) accept(MItems.PATTERN_DRIVE_CREATIVE2) + accept(MItems.PORTABLE_CONDENSATION_DRIVE) + accept(MItems.PORTABLE_DENSE_CONDENSATION_DRIVE) + fluids(MItems.FLUID_CAPSULE) fluids(MItems.FLUID_TANK) @@ -285,7 +288,7 @@ object MCreativeTabs { } fun register(event: BuildCreativeModeTabContentsEvent) { - CreativeMenuComparator.invalidate() + CreativeMenuItemComparator.invalidate() when (event.tab) { MAIN -> addMainCreativeTabItems(event) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt index 98aefba09..a7b653932 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt @@ -156,8 +156,8 @@ object MItems { ::MATTER_SCANNER, ::MATTER_PANEL, ::MATTER_REPLICATOR, ::MATTER_BOTTLER, ::ENERGY_COUNTER, ::CHEMICAL_GENERATOR, ::MATTER_RECYCLER, ::PLATE_PRESS, ::TWIN_PLATE_PRESS, ::POWERED_FURNACE, ::POWERED_BLAST_FURNACE, ::POWERED_SMOKER, - // ::STORAGE_BUS, ::STORAGE_IMPORTER, ::STORAGE_EXPORTER, ::DRIVE_VIEWER, - // ::DRIVE_RACK, ::ITEM_MONITOR, ::STORAGE_CABLE, ::STORAGE_POWER_SUPPLIER, + ::STORAGE_BUS, ::STORAGE_IMPORTER, ::STORAGE_EXPORTER, ::DRIVE_VIEWER, + ::DRIVE_RACK, ::ITEM_MONITOR, ::STORAGE_CABLE, ::STORAGE_POWER_SUPPLIER, ::ENERGY_SERVO, ::PHANTOM_ATTRACTOR, ::GRAVITATION_STABILIZER, ::COBBLESTONE_GENERATOR, ::INFINITE_WATER_SOURCE, ::ESSENCE_STORAGE, ::MATTER_RECONSTRUCTOR diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt index 8c5b0953a..c9f82926f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/API.kt @@ -121,6 +121,10 @@ interface IStorageProvider> : IStorageEventProducer { * @return copy of object, with amount of units actually extracted */ fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T + + fun extractStack(id: T, amount: BigInteger, simulate: Boolean): T { + return extractStack(get(id) ?: return storageType.empty, amount, simulate) + } } /** diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt index a76046db9..260ca07bc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/ItemStorageStack.kt @@ -1,7 +1,10 @@ package ru.dbotthepony.mc.otm.storage +import net.minecraft.network.chat.Component import net.minecraft.world.item.Item import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.getValue +import ru.dbotthepony.mc.otm.core.lazy2 import ru.dbotthepony.mc.otm.core.math.toIntSafe import java.math.BigInteger @@ -43,6 +46,8 @@ class ItemStorageStack private constructor(private val stack: ItemStack, count: override val isEmpty: Boolean = stack.isEmpty || super.isEmpty override val maxStackSize: BigInteger = BigInteger.valueOf(stack.maxStackSize.toLong()) + val hoverName: Component by lazy2({ toItemStack().hoverName }, { copy() }) + fun toItemStack(count: Int = this.count.toIntSafe()): ItemStack { return stack.copyWithCount(count) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt index 501ff399b..75081b50e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/StorageStack.kt @@ -63,9 +63,19 @@ abstract class StorageStack>(val count: BigInteger) { fun read(buff: FriendlyByteBuf): T fun write(buff: FriendlyByteBuf, value: T) - fun energyPerUpkeep(stack: T): Decimal = Decimal.ZERO + /** + * If there is not enough energy for operation, it is completely cancelled + */ fun energyPerInsert(stack: T): Decimal = energyPerOperation(stack) + + /** + * If there is not enough energy for operation, it is completely cancelled + */ fun energyPerExtract(stack: T): Decimal = energyPerOperation(stack) + + /** + * If there is not enough energy for operation, it is completely cancelled + */ fun energyPerOperation(stack: T): Decimal } @@ -113,7 +123,7 @@ abstract class StorageStack>(val count: BigInteger) { ItemStorageStack.unsafe(it.readItem(), it.readBigInteger()) }, { buff, v -> - buff.writeItem(v.toItemStack()) + buff.writeItem(v.toItemStack(1)) buff.writeBigInteger(v.count) } ) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt index 2742d6b09..62805ae1b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.storage import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectArraySet +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import ru.dbotthepony.mc.otm.core.math.isPositive import ru.dbotthepony.mc.otm.core.math.isZero import java.math.BigInteger @@ -41,23 +42,19 @@ class LocalTuple>(override var stack: T, override val id: UU } } -open class VirtualComponent>(override val storageType: StorageStack.Type) : IVirtualStorageComponent { +class VirtualComponent>(override val storageType: StorageStack.Type) : IVirtualStorageComponent { // удаленный UUID -> Кортеж - protected val remoteTuples = Object2ObjectOpenHashMap>() + private val remoteTuples = Object2ObjectOpenHashMap>() // локальный UUID -> Локальный кортеж - protected val localTuples = Object2ObjectOpenHashMap>() + private val localTuples = Object2ObjectOpenHashMap>() // Стак -> Локальный кортеж стака - protected val item2tuple = Object2ObjectOpenCustomHashMap>(StorageStack.Companion) + private val item2tuple = Object2ObjectOpenCustomHashMap>(StorageStack.Companion) - // ArrayList для скорости работы - protected val listeners: MutableSet> = ObjectArraySet() - protected val children: MutableSet> = ObjectArraySet() - protected val consumers: MutableSet> = ObjectArraySet() - - protected open fun onAdd(identity: IStorage) {} - protected open fun onRemove(identity: IStorage) {} + private val listeners = ObjectLinkedOpenHashSet>() + private val children = ObjectLinkedOpenHashSet>() + private val consumers = ObjectLinkedOpenHashSet>() override fun add(identity: IStorage) { require(identity.storageType == storageType) { "Attempt to add component of type ${identity.storageType} to virtual component of type $storageType" } @@ -76,8 +73,6 @@ open class VirtualComponent>(override val storageType: Stora if (identity is IStorageAcceptor) { consumers.add(identity) } - - onAdd(identity) } } @@ -98,8 +93,6 @@ open class VirtualComponent>(override val storageType: Stora if (identity is IStorageAcceptor) { consumers.remove(identity) } - - onRemove(identity) } } @@ -231,4 +224,8 @@ open class VirtualComponent>(override val storageType: Stora return this.storageType.empty } + + override fun toString(): String { + return "VirtualComponent[$storageType; listeners: ${listeners.size}; size: ${localTuples.size}]" + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredComponent.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredComponent.kt index eb5f65ab8..6fe2403f0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredComponent.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredComponent.kt @@ -13,4 +13,16 @@ class PoweredComponent>( ) : IStorageComponent, IStorageProvider by PoweredStorageProvider(parent, energy), IStorageAcceptor by PoweredStorageAcceptor(parent, energy) { override val storageType: StorageStack.Type get() = parent.storageType + + override fun equals(other: Any?): Boolean { + return other is PoweredComponent<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "PoweredComponent[$parent]" + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageAcceptor.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageAcceptor.kt index c058020c7..bc856176a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageAcceptor.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageAcceptor.kt @@ -6,6 +6,18 @@ import ru.dbotthepony.mc.otm.storage.StorageStack import java.util.function.Supplier class PoweredStorageAcceptor>(val parent: IStorageAcceptor, val energy: Supplier) : IStorageAcceptor by parent { + override fun equals(other: Any?): Boolean { + return other is PoweredStorageAcceptor<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "PoweredStorageAcceptor[$parent]" + } + override fun insertStack(stack: T, simulate: Boolean): T { val leftover = parent.insertStack(stack, true) if (leftover == stack) return stack diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageProvider.kt index bae9d933d..417b3ffdf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredStorageProvider.kt @@ -1,6 +1,10 @@ package ru.dbotthepony.mc.otm.storage.powered +import it.unimi.dsi.fastutil.HashCommon.mix +import it.unimi.dsi.fastutil.objects.ObjectArraySet +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer import ru.dbotthepony.mc.otm.storage.IStorageProvider import ru.dbotthepony.mc.otm.storage.StorageStack import java.math.BigInteger @@ -8,8 +12,57 @@ import java.util.* import java.util.function.Supplier class PoweredStorageProvider>(val parent: IStorageProvider, val energy: Supplier) : IStorageProvider by parent { + override fun equals(other: Any?): Boolean { + return other is PoweredStorageProvider<*> && other.parent == parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "PoweredStorageProvider[$parent]" + } + + private class Proxy>(private val parent: IStorageEventConsumer, private val powered: PoweredStorageProvider) : IStorageEventConsumer { + override val storageType: StorageStack.Type + get() = parent.storageType + + override fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider) { + parent.onStackAdded(stack, id, powered) + } + + override fun onStackChanged(stack: T, id: UUID) { + parent.onStackChanged(stack, id) + } + + override fun onStackRemoved(id: UUID) { + parent.onStackRemoved(id) + } + + override fun toString(): String { + return "PoweredStorageProvider.Proxy[$parent with $powered]" + } + + override fun equals(other: Any?): Boolean { + return other is Proxy<*> && parent == other.parent && powered == other.powered + } + + override fun hashCode(): Int { + return mix(parent.hashCode() * 31 + powered.hashCode()) + } + } + + override fun addListener(listener: IStorageEventConsumer): Boolean { + return parent.addListener(Proxy(listener, this)) + } + + override fun removeListener(listener: IStorageEventConsumer): Boolean { + return parent.removeListener(Proxy(listener, this)) + } + override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T { - val extracted = parent.extractStack(id, amount, simulate) + val extracted = parent.extractStack(id, amount, true) if (extracted.isEmpty) return extracted val energy = energy.get() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredVirtualComponent.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredVirtualComponent.kt index 5f2391019..9975847cf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredVirtualComponent.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/powered/PoweredVirtualComponent.kt @@ -16,6 +16,18 @@ class PoweredVirtualComponent>( ) : IVirtualStorageComponent, IStorageComponent by PoweredComponent(parent, energy) { constructor(type: StorageStack.Type, energy: Supplier) : this(VirtualComponent(type), energy) + override fun equals(other: Any?): Boolean { + return other is PoweredVirtualComponent<*> && parent == other.parent + } + + override fun hashCode(): Int { + return parent.hashCode() + } + + override fun toString(): String { + return "PoweredVirtualComponent[$parent]" + } + override fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider) = parent.onStackAdded(stack, id, provider) override fun onStackChanged(stack: T, id: UUID) = parent.onStackChanged(stack, id) override fun onStackRemoved(id: UUID) = parent.onStackRemoved(id)