From 441f358e121487e805da67877f58ecda289805fe Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Mon, 6 Mar 2023 00:20:59 +0700 Subject: [PATCH] Matter panel sorting (with buttons), cleanup nbt extensions --- .../mc/otm/datagen/lang/English.kt | 8 ++ .../mc/otm/datagen/lang/Russian.kt | 8 ++ .../block/entity/MatteryPoweredBlockEntity.kt | 7 - .../mc/otm/block/entity/RedstoneControl.kt | 4 +- .../entity/blackhole/BlackHoleBlockEntity.kt | 4 +- .../entity/matter/MatterPanelBlockEntity.kt | 60 ++++++-- .../entity/storage/ItemMonitorBlockEntity.kt | 8 +- .../entity/tech/EnergyCounterBlockEntity.kt | 3 +- .../capability/drive/AbstractMatteryDrive.kt | 7 +- .../energy/BlockEnergyStorageImpl.kt | 8 +- .../mc/otm/client/render/WidgetLocation.kt | 2 +- .../mc/otm/client/render/Widgets18.kt | 12 +- .../client/screen/matter/MatterPanelScreen.kt | 43 +++++- .../otm/client/screen/panels/EditablePanel.kt | 38 ++--- .../mc/otm/client/screen/panels/FramePanel.kt | 12 ++ .../button/BooleanRectangleButtonPanel.kt | 46 +++++- .../client/screen/panels/button/Buttons.kt | 135 +++++++++++------- .../panels/button/EnumRectangleButtonPanel.kt | 6 +- .../LargeBooleanRectangleButtonPanel.kt | 5 +- .../mc/otm/container/ItemFilter.kt | 4 +- .../mc/otm/container/MatteryContainer.kt | 4 +- .../kotlin/ru/dbotthepony/mc/otm/core/Ext.kt | 70 ++++++++- .../mc/otm/core/nbt/CompoundTagExt.kt | 55 ++----- .../mc/otm/core/util/ItemSorter.kt | 83 +++++++++++ .../mc/otm/item/PatternStorageItem.kt | 18 +-- .../otm/item/PortableCondensationDriveItem.kt | 4 +- .../mc/otm/menu/matter/MatterPanelMenu.kt | 59 ++++---- .../mc/otm/network/FieldSynchronizer.kt | 5 +- .../mc/otm/registry/MCreativeTabs.kt | 3 + .../textures/gui/widgets/storage_controls.png | Bin 711 -> 803 bytes .../textures/gui/widgets/storage_controls.xcf | Bin 3189 -> 4408 bytes .../mc/otm/tests/ComparatorTests.kt | 34 +++++ 32 files changed, 534 insertions(+), 221 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt create mode 100644 src/test/kotlin/ru/dbotthepony/mc/otm/tests/ComparatorTests.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 72e387605..4305eb16f 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 @@ -638,6 +638,14 @@ private fun gui(provider: MatteryLanguageProvider) { gui("side_mode.pull", "Pull") gui("side_mode.push", "Push") + + gui("sorting.default", "Default sorting") + 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.ascending", "Ascending") + gui("sorting.descending", "Descending") } } 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 e1390b173..8a2872690 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 @@ -643,6 +643,14 @@ private fun gui(provider: MatteryLanguageProvider) { gui("side_mode.pull", "Автоматическое вытягивание") gui("side_mode.push", "Автоматическое выталкивание") + + gui("sorting.default", "Сортировка по умолчанию") + gui("sorting.name", "Сортировка по имени") + gui("sorting.id", "Сортировка по ID") + gui("sorting.modid", "Сортировка по пространству имён (моду)") + gui("sorting.count", "Сортировка по количеству") + gui("sorting.ascending", "Возрастающая") + gui("sorting.descending", "Убывающая") } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt index 05de98e43..9a7390335 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryPoweredBlockEntity.kt @@ -2,30 +2,23 @@ package ru.dbotthepony.mc.otm.block.entity import net.minecraft.ChatFormatting import net.minecraft.core.BlockPos -import net.minecraft.core.Direction import net.minecraft.nbt.CompoundTag import net.minecraft.network.chat.Component import net.minecraft.world.item.BlockItem import net.minecraft.world.item.ItemStack import net.minecraft.world.item.TooltipFlag import net.minecraft.world.level.BlockGetter -import net.minecraft.world.level.Level import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.ForgeCapabilities -import net.minecraftforge.common.util.LazyOptional import ru.dbotthepony.mc.otm.capability.* -import ru.dbotthepony.mc.otm.capability.energy.BlockEnergyStorageImpl import ru.dbotthepony.mc.otm.capability.energy.ItemEnergyStorageImpl import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.nbt.ifHas import ru.dbotthepony.mc.otm.core.ifPresentK import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.nbt.map -import ru.dbotthepony.mc.otm.core.nbt.set abstract class MatteryPoweredBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(p_155228_, p_155229_, p_155230_) { val batteryContainer = MatteryContainer(::setChangedLight, 1).also(::addDroppableContainer) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt index e6d4c3180..4650d6263 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt @@ -2,7 +2,7 @@ package ru.dbotthepony.mc.otm.block.entity import net.minecraft.nbt.CompoundTag import net.minecraftforge.common.util.INBTSerializable -import ru.dbotthepony.mc.otm.core.nbt.getEnum +import ru.dbotthepony.mc.otm.core.nbt.mapString import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.network.FieldSynchronizer @@ -30,7 +30,7 @@ abstract class AbstractRedstoneControl : INBTSerializable { return } - redstoneSetting = nbt.getEnum(SETTING_KEY) + redstoneSetting = nbt.mapString(SETTING_KEY, RedstoneSetting::valueOf, RedstoneSetting.LOW) redstoneSignal = nbt.getInt(SIGNAL_KEY) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt index 52df29f0b..e95438428 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt @@ -27,7 +27,7 @@ import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MItems import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.core.math.getSphericalBlockPositions -import ru.dbotthepony.mc.otm.core.nbt.mapIf +import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.matter.MatterManager import ru.dbotthepony.mc.otm.triggers.BlackHoleTrigger @@ -164,7 +164,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mattery override fun load(tag: CompoundTag) { super.load(tag) - mass = tag.mapIf("mass", Decimal::deserializeNBT) ?: BASELINE_MASS + mass = tag.map("mass", Decimal::deserializeNBT) ?: BASELINE_MASS spinDirection = tag.getBoolean("spin_direction") } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt index 86e34f8c2..7aabff5bb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt @@ -1,5 +1,7 @@ package ru.dbotthepony.mc.otm.block.entity.matter +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import net.minecraft.core.BlockPos import net.minecraft.world.level.block.state.BlockState import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu @@ -11,13 +13,17 @@ import java.util.HashMap import java.util.UUID import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag -import net.minecraft.nbt.Tag -import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.Level -import ru.dbotthepony.mc.otm.core.TranslatableComponent +import net.minecraftforge.common.util.INBTSerializable import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.capability.matter.* +import ru.dbotthepony.mc.otm.core.nbt.getBoolean +import ru.dbotthepony.mc.otm.core.nbt.getCompoundList +import ru.dbotthepony.mc.otm.core.nbt.map +import ru.dbotthepony.mc.otm.core.nbt.mapString +import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.ItemSorter import ru.dbotthepony.mc.otm.graph.Graph6Node import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphNode import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph @@ -28,6 +34,26 @@ import java.util.stream.Stream class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.MATTER_PANEL, p_155229_, p_155230_), IMatterGraphNode, IReplicationTaskProvider { + class PlayerSettings(var sorter: ItemSorter = ItemSorter.DEFAULT, var ascending: Boolean = true) : INBTSerializable { + override fun serializeNBT(): CompoundTag { + return CompoundTag().also { + it["sorter"] = sorter.name + it["ascending"] = ascending + } + } + + override fun deserializeNBT(nbt: CompoundTag) { + sorter = nbt.mapString("sorter", ItemSorter::valueOf, ItemSorter.DEFAULT) + ascending = nbt.getBoolean("ascending", true) + } + } + + private val playerSettings = Object2ObjectOpenHashMap() + + fun getPlayerSettings(ply: Player): PlayerSettings { + return playerSettings.computeIfAbsent(ply.uuid, Object2ObjectFunction { PlayerSettings() }) + } + private val listeners = ArrayList() override val matterNode = Graph6Node(this) @@ -39,9 +65,6 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : listeners.remove(menu) } - override val defaultDisplayName: Component - get() = MACHINE_NAME - override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return MatterPanelMenu(containerID, inventory, this) } @@ -131,13 +154,21 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : list.add(task.serializeNBT()) } - nbt.put("tasks", list) + nbt["tasks"] = list + + val settings = CompoundTag() + + for ((uuid, value) in playerSettings) { + settings[uuid.toString()] = value.serializeNBT() + } + + nbt["settings"] = settings } override fun load(nbt: CompoundTag) { super.load(nbt) _tasks.clear() - val list = nbt.getList("tasks", Tag.TAG_COMPOUND.toInt()) + val list = nbt.getCompoundList("tasks") for (tag in list) { val task = ReplicationTask.deserializeNBT(tag) @@ -146,6 +177,15 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : _tasks[task.id] = task } } + + playerSettings.clear() + + nbt.map("settings") { it: CompoundTag -> + for (k in it.allKeys) { + playerSettings.computeIfAbsent(UUID.fromString(k), Object2ObjectFunction { PlayerSettings() }) + .deserializeNBT(it.getCompound(k)) + } + } } override fun getTask(id: UUID): ReplicationTask? { @@ -184,8 +224,4 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : _tasks.clear() } - - companion object { - private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.matter_panel") - } } 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 224c4df97..614b60b56 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 @@ -35,7 +35,7 @@ import ru.dbotthepony.mc.otm.graph.storage.StorageNetworkGraph 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.getEnum +import ru.dbotthepony.mc.otm.core.nbt.mapString import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.menu.storage.ItemMonitorMenu import ru.dbotthepony.mc.otm.storage.* @@ -93,9 +93,9 @@ class ItemMonitorPlayerSettings : INBTSerializable, MatteryPacket { } override fun deserializeNBT(nbt: CompoundTag) { - ingredientPriority = nbt.getEnum(INGREDIENT_PRIORITY_KEY) - resultTarget = nbt.getEnum(RESULT_TARGET_KEY) - craftingAmount = nbt.getEnum(QUICK_CRAFT_AMOUNT_KEY) + 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) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt index 82fcf0fee..3819c32bb 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/EnergyCounterBlockEntity.kt @@ -20,7 +20,6 @@ import ru.dbotthepony.mc.otm.core.math.BlockRotation import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.getDecimal import ru.dbotthepony.mc.otm.core.nbt.getByteArrayList -import ru.dbotthepony.mc.otm.core.nbt.ifHas import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.menu.tech.EnergyCounterMenu @@ -101,7 +100,7 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat passed = nbt.getDecimal(PASSED_ENERGY_KEY) ioLimit = nbt.map(IO_LIMIT_KEY, Decimal.Companion::deserializeNBT) - nbt.ifHas(POWER_HISTORY_POINTER_KEY, IntTag::class.java) { + nbt.map(POWER_HISTORY_POINTER_KEY) { it: IntTag -> historyTick = it.asInt } 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 60f568e8c..fec2c6b25 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 @@ -11,7 +11,7 @@ import ru.dbotthepony.mc.otm.core.* import ru.dbotthepony.mc.otm.core.math.BigInteger import ru.dbotthepony.mc.otm.core.math.isPositive import ru.dbotthepony.mc.otm.core.math.serializeNBT -import ru.dbotthepony.mc.otm.core.nbt.ifHas +import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.storage.* import java.math.BigInteger @@ -176,10 +176,7 @@ abstract class AbstractMatteryDrive @JvmOverloads constructor storedDifferentStacks = 0 // nextID = 0L - nbt.ifHas("capacity") { - driveCapacity = BigInteger(it) - } - + driveCapacity = nbt.map("capacity", ::BigInteger) ?: driveCapacity maxDifferentStacks = nbt.getInt("max_different_stacks") for (entry in nbt.getList("items", Tag.TAG_COMPOUND.toInt())) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt index 9afcbe58d..26ae42578 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/energy/BlockEnergyStorageImpl.kt @@ -16,12 +16,10 @@ import net.minecraftforge.common.util.LazyOptional import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity import ru.dbotthepony.mc.otm.config.ConciseBalanceValues import ru.dbotthepony.mc.otm.config.VerboseBalanceValues -import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.defineDecimal import ru.dbotthepony.mc.otm.core.nbt.map -import ru.dbotthepony.mc.otm.core.nbt.mapIf import ru.dbotthepony.mc.otm.core.nbt.set sealed class BlockEnergyStorageImpl( @@ -142,9 +140,9 @@ sealed class BlockEnergyStorageImpl( override fun deserializeNBT(nbt: CompoundTag?) { if (nbt == null) return batteryLevel = nbt.map(ENERGY_STORED_KEY, Decimal.Companion::deserializeNBT) ?: Decimal.ZERO - maxBatteryLevelStorage = nbt.mapIf(ENERGY_STORED_MAX_KEY, Decimal.Companion::deserializeNBT) - maxInputStorage = nbt.mapIf(MAX_INPUT_KEY, Decimal.Companion::deserializeNBT) - maxOutputStorage = nbt.mapIf(MAX_OUTPUT_KEY, Decimal.Companion::deserializeNBT) + maxBatteryLevelStorage = nbt.map(ENERGY_STORED_MAX_KEY, Decimal.Companion::deserializeNBT) + maxInputStorage = nbt.map(MAX_INPUT_KEY, Decimal.Companion::deserializeNBT) + maxOutputStorage = nbt.map(MAX_OUTPUT_KEY, Decimal.Companion::deserializeNBT) } var resolver: LazyOptional = LazyOptional.of { this } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt index 5b13d0273..2588604cf 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/WidgetLocation.kt @@ -5,7 +5,7 @@ import ru.dbotthepony.mc.otm.OverdriveThatMatters object WidgetLocation { val LARGE_BUTTON = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/large_button.png"), 72f, 18f) - val STORAGE_CONTROLS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/storage_controls.png"), 90f, 18f) + val STORAGE_CONTROLS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/storage_controls.png"), 90f, 36f) val MISC_18 = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/misc18.png"), 72f, 72f) val SLOT_BACKGROUNDS = MatteryAtlas(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets/slot_backgrounds.png"), 72f, 72f) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt index 603a47cb6..09c145a92 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/render/Widgets18.kt @@ -34,12 +34,14 @@ object Widgets18 { val BUTTON_DISABLED_STRETCHABLE = makeButton(buttonGrids) val BUTTON_DISABLED = buttonGrids.next() - private val storageGrid = WidgetLocation.STORAGE_CONTROLS.grid(rows = 1, columns = 5) + private val storageGrid = WidgetLocation.STORAGE_CONTROLS.grid(rows = 2, columns = 5) val ARROW_DOWN = storageGrid.next() - val AZ = storageGrid.next() - val COUNT = storageGrid.next() - val COLON = storageGrid.next() - val C = storageGrid.next() + val ARROW_UP = storageGrid.next() + val SORT_DEFAULT = storageGrid.next() + val SORT_ALPHABET = storageGrid.next() + val SORT_COUNT = storageGrid.next() + val SORT_MODID = storageGrid.next() + val SORT_ID = storageGrid.next() private val miscGrid = WidgetLocation.MISC_18.grid(4, 4) 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 75c0a72fe..f5c9b847f 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 @@ -8,15 +8,20 @@ import net.minecraft.world.item.ItemStack import ru.dbotthepony.mc.otm.capability.matter.IPatternState import ru.dbotthepony.mc.otm.capability.matter.IReplicationTask import ru.dbotthepony.mc.otm.client.render.WidgetLocation +import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.* import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel +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.input.EditBoxPanel 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.ScrollBarConstants import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.math.integerDivisionDown +import ru.dbotthepony.mc.otm.core.util.ItemSorter import ru.dbotthepony.mc.otm.menu.matter.MatterPanelMenu import ru.dbotthepony.mc.otm.menu.matter.ReplicationRequestPacket import ru.dbotthepony.mc.otm.network.MenuNetworkChannel @@ -28,26 +33,50 @@ class MatterPanelScreen( inventory: Inventory, title: Component ) : MatteryScreen(menu, inventory, title) { - override fun makeMainFrame(): FramePanel> { + override fun makeMainFrame(): FramePanel> { var isPatternView = true + var scrollPatterns = 0 + var scrollTasks = 0 val frame = FramePanel.padded(this, null, GRID_WIDTH * AbstractSlotPanel.SIZE + ScrollBarConstants.WIDTH + 4f, GRID_HEIGHT * AbstractSlotPanel.SIZE, title) + 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) + } + val scrollBar = DiscreteScrollBarPanel(this, frame, { if (isPatternView) { - integerDivisionDown(menu.patterns.size, GRID_WIDTH) + scrollPatterns = integerDivisionDown(menu.patterns.size, GRID_WIDTH) + scrollPatterns } else { - integerDivisionDown(menu.tasks.size, GRID_WIDTH) + scrollTasks = integerDivisionDown(menu.tasks.size, GRID_WIDTH) + scrollTasks } }, { _, _, _ -> }) scrollBar.dock = Dock.RIGHT - frame.Tab(onOpen = { isPatternView = true }, activeIcon = PATTERN_LIST_ACTIVE, inactiveIcon = PATTERN_LIST_INACTIVE).also { + frame.Tab(onOpen = { isPatternView = true; scrollBar.scroll = scrollPatterns }, activeIcon = PATTERN_LIST_ACTIVE, inactiveIcon = PATTERN_LIST_INACTIVE).also { it.tooltip = TranslatableComponent("otm.container.matter_panel.patterns") } - frame.Tab(onOpen = { isPatternView = false }, activeIcon = TASK_LIST_ACTIVE, inactiveIcon = TASK_LIST_INACTIVE).also { + frame.Tab(onOpen = { isPatternView = false; scrollBar.scroll = scrollTasks }, activeIcon = TASK_LIST_ACTIVE, inactiveIcon = TASK_LIST_INACTIVE).also { it.tooltip = TranslatableComponent("otm.container.matter_panel.tasks") } @@ -133,6 +162,8 @@ class MatterPanelScreen( private fun openTask(task: IReplicationTask<*>) { val frame = FramePanel.padded(this, null, 170f, 20f, TranslatableComponent("otm.container.matter_panel.task")) + frame.closeOnEscape = true + object : AbstractSlotPanel(this@MatterPanelScreen, frame) { init { dock = Dock.LEFT @@ -175,6 +206,8 @@ class MatterPanelScreen( private fun openPattern(pattern: IPatternState) { val frame = FramePanel.padded(this, null, 213f, (ButtonPanel.HEIGHT + 3f) * 4f, TranslatableComponent("otm.container.matter_panel.task")) + frame.closeOnEscape = true + val rowTop = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) val rowInput = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) val rowBottom = EditablePanel(this, frame, height = ButtonPanel.HEIGHT) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt index 23b8853fb..5f7f9d528 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/EditablePanel.kt @@ -345,10 +345,7 @@ open class EditablePanel @JvmOverloads constructor( fun flash() { isFlashing = true - - if (parent == null) { - popup() - } + popup() } val isFlashFrame: Boolean @@ -1338,11 +1335,7 @@ open class EditablePanel @JvmOverloads constructor( final override fun mouseClicked(x: Double, y: Double, button: Int): Boolean { if (!isVisible() || !acceptMouseInput) return false - - if (flashAnyBlocker()) { - return true - } - + if (flashAnyBlocker()) return true if (grabMouseInput) return mouseClickedInner(x, y, button) for (child in visibleChildrenInternal) { @@ -1366,9 +1359,11 @@ open class EditablePanel @JvmOverloads constructor( if (!isVisible() || !acceptMouseInput) return false if (isGrabbingMouseInput() || withinBounds(x, y)) { - if (acceptMouseInput && parent == null) popup() + popup() return mouseClicked(x, y, button) } else if (withinExtendedBounds(x, y)) { + popup(false) + for (child in visibleChildrenInternal) { if (child.mouseClickedChecked(x, y, button)) { killFocusForEverythingExcept(child) @@ -1390,11 +1385,7 @@ open class EditablePanel @JvmOverloads constructor( final override fun mouseReleased(x: Double, y: Double, button: Int): Boolean { if (!isVisible() || !acceptMouseInput) return false - - if (flashAnyBlocker(false)) { - return true - } - + if (flashAnyBlocker(false)) return true if (grabMouseInput) return mouseReleasedInner(x, y, button) for (child in visibleChildrenInternal) { @@ -1434,11 +1425,7 @@ open class EditablePanel @JvmOverloads constructor( final override fun mouseDragged(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { if (!isVisible() || !acceptMouseInput) return false - - if (flashAnyBlocker(false)) { - return true - } - + if (flashAnyBlocker(false)) return true if (grabMouseInput) return mouseDraggedInner(x, y, button, xDelta, yDelta) for (child in visibleChildrenInternal) { @@ -1472,7 +1459,6 @@ open class EditablePanel @JvmOverloads constructor( return false } - protected open fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { return true } @@ -1537,6 +1523,7 @@ open class EditablePanel @JvmOverloads constructor( } } + if (focusedAsParent) return keyPressedInternal(key, scancode, mods) return false } @@ -1560,6 +1547,7 @@ open class EditablePanel @JvmOverloads constructor( } } + if (focusedAsParent) return keyReleasedInternal(key, scancode, mods) return false } @@ -1641,14 +1629,18 @@ open class EditablePanel @JvmOverloads constructor( isRemoved = true } - fun popup() { + fun popup(focus: Boolean = true) { if (isRemoved) { return } - if (screen is MatteryScreen<*>) { + if (screen is MatteryScreen<*> && parent == null) { screen.popup(this as EditablePanel>) } + + if (focus && !isEverFocused()) { + requestFocus() + } } fun asGrid(): FlexGridPanel<*> { 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 1eb70ae8b..52d3bd56a 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 @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.client.screen.panels +import com.mojang.blaze3d.platform.InputConstants import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.narration.NarratableEntry @@ -133,6 +134,8 @@ open class FramePanel( protected var dragging = false + var closeOnEscape = false + init { setDockPadding(PADDING, if (title != null) PADDING_TOP else PADDING, PADDING, PADDING) } @@ -182,6 +185,15 @@ open class FramePanel( } + override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean { + if (key == InputConstants.KEY_ESCAPE && closeOnEscape) { + remove() + return true + } + + return super.keyPressedInternal(key, scancode, mods) + } + override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { RECTANGLE.render(stack, width = width, height = height) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt index 8d568d90c..9c5f2fbc7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/BooleanRectangleButtonPanel.kt @@ -1,11 +1,14 @@ package ru.dbotthepony.mc.otm.client.screen.panels.button import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.ChatFormatting import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.AbstractMatterySprite import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.value import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback @@ -17,9 +20,11 @@ abstract class BooleanRectangleButtonPanel( width: Float, height: Float, val prop: GetterSetter, - val skinElementActive: AbstractMatterySprite? = null, - val skinElementInactive: AbstractMatterySprite? = null, + var skinElementActive: AbstractMatterySprite? = null, + var skinElementInactive: AbstractMatterySprite? = null, val onChange: ((newValue: Boolean) -> Unit)? = null, + var tooltipActive: Component? = null, + var tooltipInactive: Component? = null, ) : RectangleButtonPanel(screen, parent, x, y, width, height, null) { override fun onClick(mouseButton: Int) { val newValue = !prop.value @@ -27,6 +32,39 @@ abstract class BooleanRectangleButtonPanel( onChange?.invoke(newValue) } + override fun innerRenderTooltips(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + if (isHovered) { + val tooltipActive = tooltipActive + val tooltipInactive = tooltipInactive + val tooltipList = tooltipList + val tooltip = tooltip + + if (tooltipActive != null || tooltipInactive != null || tooltipList != null || tooltip != null) { + val tooltips = ArrayList(2) + + if (tooltipList != null) { + tooltips.addAll(tooltipList) + tooltips.add(SPACE) + } else if (tooltip != null) { + tooltips.add(tooltip) + tooltips.add(SPACE) + } + + if (tooltipActive != null) { + tooltips.add(tooltipActive.copy().withStyle(if (prop.get()) ChatFormatting.WHITE else ChatFormatting.GRAY)) + } + + if (tooltipInactive != null) { + tooltips.add(tooltipInactive.copy().withStyle(if (!prop.get()) ChatFormatting.WHITE else ChatFormatting.GRAY)) + } + + screen.renderComponentTooltip(stack, tooltips, mouseX.toInt(), mouseY.toInt(), font) + } + } + + return super.innerRenderTooltips(stack, mouseX, mouseY, partialTick) + } + override var isDisabled: Boolean get() { if (prop is IPlayerInputWithFeedback) { @@ -46,4 +84,8 @@ abstract class BooleanRectangleButtonPanel( skinElementInactive?.render(stack, width = width, height = height) } } + + companion object { + private val SPACE = TextComponent("") + } } 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 72ef6acce..ef1e43b1f 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 @@ -5,7 +5,6 @@ import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel @@ -192,61 +191,93 @@ private fun > makeEnergyConfigPanel( return frame } -fun > makeDeviceControls( +class DeviceControls>( screen: S, parent: FramePanel, - redstone: IPlayerInputWithFeedback? = null, - itemConfig: ItemHandlerPlayerInput? = null, - energyConfig: EnergyPlayerInput? = null, -): EditablePanel { - val panel = object : EditablePanel(screen, parent, width = LargeEnumRectangleButtonPanel.SIZE, height = 0f, x = parent.width + 3f) { - override fun tickInner() { - super.tickInner() - x = parent.width + 3f - y = 0f + extra: Iterable> = listOf(), + val redstoneConfig: IPlayerInputWithFeedback? = null, + val itemConfig: ItemHandlerPlayerInput? = null, + val energyConfig: EnergyPlayerInput? = null, +) : EditablePanel(screen, parent, x = parent.width + 3f, height = 0f, width = 0f) { + val itemConfigButton: LargeRectangleButtonPanel? + val energyConfigButton: LargeRectangleButtonPanel? + val redstoneControlsButton: LargeEnumRectangleButtonPanel? + private var nextY = 0f + + fun

> addButton(button: P): P { + button.parent = this + button.x = 0f + button.y = nextY + nextY += button.height + 2f + height = nextY - 2f + width = button.width.coerceAtLeast(width) + return button + } + + init { + for (button in extra) { + addButton(button) + } + + if (redstoneConfig != null) { + redstoneControlsButton = addButton(makeRedstoneSettingButton(screen, this, control = redstoneConfig)) + } else { + redstoneControlsButton = null + } + + if (itemConfig != null) { + itemConfigButton = addButton(object : LargeRectangleButtonPanel(screen, this@DeviceControls, skinElement = Widgets18.ITEMS_CONFIGURATION) { + init { + tooltip = TranslatableComponent("otm.gui.sides.item_config") + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + val frame = makeItemHandlerControlPanel(screen, itemConfig) + + frame.x = absoluteX + width / 2f - frame.width / 2f + frame.y = absoluteY + height + 8f + } + } + }) + } else { + itemConfigButton = null + } + + if (energyConfig != null) { + energyConfigButton = addButton(object : LargeRectangleButtonPanel(screen, this@DeviceControls, y = nextY, skinElement = Widgets18.ENERGY_CONFIGURATION) { + init { + tooltip = TranslatableComponent("otm.gui.sides.energy_config") + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + val frame = makeEnergyConfigPanel(screen, energyConfig) + + frame.x = absoluteX + width / 2f - frame.width / 2f + frame.y = absoluteY + height + 8f + } + } + }) + } else { + energyConfigButton = null } } - var y = 0f - - if (redstone != null) { - y += makeRedstoneSettingButton(screen, panel, y = y, control = redstone).height + 2f + override fun tickInner() { + super.tickInner() + x = (parent?.width ?: 0f) + 3f + y = 0f } - - if (itemConfig != null) { - y += object : LargeRectangleButtonPanel(screen, panel, y = y, skinElement = Widgets18.ITEMS_CONFIGURATION) { - init { - tooltip = TranslatableComponent("otm.gui.sides.item_config") - } - - override fun onClick(mouseButton: Int) { - if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { - val frame = makeItemHandlerControlPanel(screen, itemConfig) - - frame.x = absoluteX + width / 2f - frame.width / 2f - frame.y = absoluteY + height + 8f - } - } - }.height + 2f - } - - if (energyConfig != null) { - y += object : LargeRectangleButtonPanel(screen, panel, y = y, skinElement = Widgets18.ENERGY_CONFIGURATION) { - init { - tooltip = TranslatableComponent("otm.gui.sides.energy_config") - } - - override fun onClick(mouseButton: Int) { - if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { - val frame = makeEnergyConfigPanel(screen, energyConfig) - - frame.x = absoluteX + width / 2f - frame.width / 2f - frame.y = absoluteY + height + 8f - } - } - }.height + 2f - } - - panel.height = (y - 2f).coerceAtLeast(0f) - return panel +} + +fun > makeDeviceControls( + screen: S, + parent: FramePanel, + extra: Iterable> = listOf(), + redstone: IPlayerInputWithFeedback? = null, + itemConfig: ItemHandlerPlayerInput? = null, + energyConfig: EnergyPlayerInput? = null, +): DeviceControls { + return DeviceControls(screen, parent, extra = extra, redstoneConfig = redstone, itemConfig = itemConfig, energyConfig = energyConfig) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt index b699f1bfb..894809386 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/EnumRectangleButtonPanel.kt @@ -158,12 +158,14 @@ abstract class EnumRectangleButtonPanel>( } val listing = ArrayList() + val tooltipList = tooltipList + val tooltip = tooltip if (tooltipList != null) { - listing.addAll(tooltipList!!) + listing.addAll(tooltipList) listing.add(SPACE) } else if (tooltip != null) { - listing.add(tooltip!!) + listing.add(tooltip) listing.add(SPACE) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt index 531f520cc..f2003f88f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/button/LargeBooleanRectangleButtonPanel.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.mc.otm.client.screen.panels.button import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.AbstractMatterySprite import ru.dbotthepony.mc.otm.client.render.Widgets18 @@ -19,7 +20,9 @@ open class LargeBooleanRectangleButtonPanel( skinElementActive: AbstractMatterySprite? = null, skinElementInactive: AbstractMatterySprite? = null, onChange: ((newValue: Boolean) -> Unit)? = null, -) : BooleanRectangleButtonPanel(screen, parent, x, y, width, height, prop, skinElementActive, skinElementInactive, onChange) { + tooltipActive: Component? = null, + tooltipInactive: Component? = null, +) : BooleanRectangleButtonPanel(screen, parent, x, y, width, height, prop, skinElementActive, skinElementInactive, onChange, tooltipActive, tooltipInactive) { final override val IDLE = Widgets18.BUTTON_IDLE final override val HOVERED = Widgets18.BUTTON_HOVERED final override val PRESSED = Widgets18.BUTTON_PRESSED diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt index 5e6e69ac2..668f7d1c5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemFilter.kt @@ -11,7 +11,7 @@ import net.minecraftforge.common.util.INBTSerializable import net.minecraftforge.network.NetworkEvent import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.client.minecraft -import ru.dbotthepony.mc.otm.core.nbt.ifHas +import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.core.nbt.set @@ -266,7 +266,7 @@ class ItemFilter( if (nbt == null) return - nbt.ifHas("items", ListTag::class.java) { + nbt.map("items") { it: ListTag -> for ((i, value) in it.withIndex()) { if (value is CompoundTag) { filter[i] = ItemStack.of(value) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt index 5ed8462ec..f2bc86f6e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/MatteryContainer.kt @@ -10,7 +10,7 @@ import kotlin.jvm.JvmOverloads import net.minecraft.world.entity.player.Player import net.minecraft.world.level.block.entity.BlockEntity import net.minecraftforge.common.util.INBTSerializable -import ru.dbotthepony.mc.otm.core.nbt.ifHas +import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.set import java.util.* @@ -53,7 +53,7 @@ open class MatteryContainer(val watcher: Runnable, private val size: Int) : Cont } // нам не интересен размер - tag.ifHas("items", ListTag::class.java) { + tag.map("items") { it: ListTag -> for (i in 0 until it.size.coerceAtMost(size)) { slots[i] = ItemStack.of(it[i] as CompoundTag) } 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 efbf4442b..e35bea5f9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -33,7 +33,6 @@ import net.minecraftforge.registries.IForgeRegistry import ru.dbotthepony.mc.otm.core.math.Vector import ru.dbotthepony.mc.otm.core.util.readInt import java.io.InputStream -import java.io.OutputStream import java.lang.ref.Reference import java.math.BigInteger import java.util.Arrays @@ -313,3 +312,72 @@ fun Stream.asIterable(): Iterable { } } } + +// Kotlin type safety: +// since Java generics are invariant, +// and can not distinguish between null and non-null type parameters +// we need to tell compiler that Comparator.nullsFirst actually has next signature: +// fun Comparator.nullsFirst(original: Comparator): Comparator +fun Comparator.nullsFirst(): Comparator { + return Comparator.nullsFirst(this as Comparator) +} + +fun Comparator.nullsLast(): Comparator { + return Comparator.nullsLast(this as Comparator) +} + +/** + * 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 { + require(toIndex >= fromIndex) { "Invalid range: to $toIndex >= from $fromIndex" } + require(fromIndex >= 0) { "Invalid from index: $fromIndex" } + require(toIndex >= 0) { "Invalid to index: $toIndex" } + require(fromIndex <= size) { "Invalid from index: $fromIndex (list size $size)" } + require(toIndex <= size) { "Invalid to index: $toIndex (list size $size)" } + + if (fromIndex == size || fromIndex == toIndex || comparator.compare(element, this[fromIndex]) <= 0) { + return fromIndex + } + + if (toIndex - fromIndex <= 10) { + for (i in fromIndex + 1 until toIndex) { + val compare = comparator.compare(element, this[i]) + + if (compare <= 0) { + return i + } + } + + return size + } else { + var lower = fromIndex + var upper = toIndex - 1 + + while (upper - lower >= 10) { + val middle = (upper + lower) / 2 + val compare = comparator.compare(element, this[middle]) + + if (compare == 0) { + return middle + } else if (compare < 0) { + upper = middle + } else { + lower = middle + } + } + + return searchInsertionIndex(element, comparator, lower, upper + 1) + } +} + +/** + * Inserts [element] into [MutableList] at index determined by [comparator] + * + * If [MutableList] is not sorted, result of this function is undefined + */ +fun MutableList.addSorted(element: E, comparator: Comparator) { + add(searchInsertionIndex(element, comparator), element) +} 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 ae8e344da..6c4ab1256 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 @@ -15,6 +15,7 @@ import net.minecraft.nbt.LongArrayTag import net.minecraft.nbt.LongTag import net.minecraft.nbt.NbtAccounter import net.minecraft.nbt.NbtUtils +import net.minecraft.nbt.NumericTag import net.minecraft.nbt.ShortTag import net.minecraft.nbt.StringTag import net.minecraft.nbt.Tag @@ -58,57 +59,13 @@ inline fun CompoundTag.map(key: String, consumer: (T) -> R return null } -inline fun CompoundTag.mapIf(key: String, consumer: (T) -> R): R? { - val tag = get(key) - - if (tag is T) { - return consumer(tag) - } - - return null -} - -inline fun > CompoundTag.getEnum(key: String, ifNothing: () -> R = { R::class.java.enumConstants[0] }): R { - val tag = get(key) - - if (tag is StringTag) { - val str = tag.asString - return R::class.java.enumConstants.first { it.name == str } - } - - return ifNothing.invoke() +fun CompoundTag.mapString(index: String, mapper: (String) -> T, orElse: T): T { + val tag = this[index] as? StringTag ?: return orElse + return mapper.invoke(tag.asString) } fun CompoundTag.getItemStack(key: String): ItemStack = map(key, ItemStack::of) ?: ItemStack.EMPTY -inline fun CompoundTag.ifHas(s: String, consumer: (Tag) -> Unit) { - val tag = get(s) - - if (tag != null) { - consumer(tag) - } -} - -inline fun CompoundTag.ifHas(s: String, type: Byte, consumer: (Tag) -> Unit) { - val tag = get(s) - - if (tag != null && tag.id == type) { - consumer(tag) - } -} - -inline fun CompoundTag.ifHas(s: String, type: Class, consumer: (T) -> Unit) { - val tag = get(s) - - if (tag != null && tag::class.java === type) { - consumer(tag as T) - } -} - -fun CompoundTag.getList(key: String): ListTag { - return this[key] as? ListTag ?: ListTag() -} - @Suppress("unchecked_cast") // type is checked inside getList fun CompoundTag.getByteList(key: String): MutableList = getList(key, Tag.TAG_BYTE.toInt()) as MutableList @Suppress("unchecked_cast") // type is checked inside getList @@ -153,3 +110,7 @@ fun CompoundTag.getJson(key: String, sizeLimit: NbtAccounter = NbtAccounter.UNLI return FastByteArrayInputStream(bytes).readJson(sizeLimit) } + +fun CompoundTag.getBoolean(index: String, orElse: Boolean): Boolean { + return (this[index] as? NumericTag)?.asInt?.let { it > 0 } ?: orElse +} 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 new file mode 100644 index 000000000..76a467de8 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/ItemSorter.kt @@ -0,0 +1,83 @@ +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.world.item.Item +import net.minecraftforge.common.CreativeModeTabRegistry +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 + +object CreativeMenuComparator : Comparator { + override fun compare(o1: Item, o2: Item): Int { + rebuild() + return item2index.getInt(o1).compareTo(item2index.getInt(o2)) + } + + private val item2index = Reference2IntOpenHashMap() + + init { + item2index.defaultReturnValue(Int.MAX_VALUE) + } + + private fun rebuild() { + if (item2index.isEmpty()) { + var i = 0 + + for (tab in CreativeModeTabRegistry.getSortedCreativeModeTabs()) { + for (item in tab.displayItems) { + item2index.computeIfAbsent(item.item, Reference2IntFunction { i++ }) + } + } + } + } + + internal fun invalidate() { + item2index.clear() + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemLocalizedNameComparator : Comparator { + override fun compare(o1: Item, o2: Item): Int { + return o1.description.string.compareTo(o2.description.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 + val b = o2.registryName?.namespace ?: return 0 + return a.compareTo(b) + } + + val NullsFirst = nullsFirst() + val NullsLast = nullsLast() +} + +object ItemIDComparator : Comparator { + override fun compare(o1: Item, o2: Item): Int { + val a = o1.registryName ?: return 0 + val b = o2.registryName ?: return 0 + return a.compareTo(b) + } + + val NullsFirst = nullsFirst() + 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")); + + val title: Component get() = sTitle.copy() +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PatternStorageItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PatternStorageItem.kt index f1812f5b9..db5c2a7d5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PatternStorageItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PatternStorageItem.kt @@ -16,7 +16,7 @@ import net.minecraft.world.level.Level import net.minecraftforge.common.capabilities.Capability import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.capability.matter.* -import ru.dbotthepony.mc.otm.core.nbt.ifHas +import ru.dbotthepony.mc.otm.core.nbt.map import java.util.* import java.util.stream.Stream @@ -99,11 +99,9 @@ class PatternStorageItem : Item { } override val storedPatterns: Int get() { - stack.tag?.ifHas("otm_patterns", ListTag::class.java) { - return it.size - } - - return 0 + return stack.tag?.map("otm_patterns") { it: ListTag -> + it.size + } ?: 0 } override fun getCapability(cap: Capability, side: Direction?): LazyOptional { @@ -111,11 +109,9 @@ class PatternStorageItem : Item { } override val patterns: Stream get() { - stack.tag?.ifHas("otm_patterns", ListTag::class.java) { - return it.stream().map { PatternState.deserializeNBT(it) }.filter { it != null } as Stream - } - - return Stream.empty() + return stack.tag?.map("otm_patterns") { it: ListTag -> + it.stream().map { PatternState.deserializeNBT(it) }.filter { it != null } as Stream + } ?: Stream.empty() } override fun insertPattern( diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt index 4aa4e152c..fe5d55909 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/PortableCondensationDriveItem.kt @@ -21,7 +21,7 @@ import net.minecraftforge.event.entity.player.EntityItemPickupEvent import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.capability.drive.DrivePool import ru.dbotthepony.mc.otm.container.ItemFilter -import ru.dbotthepony.mc.otm.core.nbt.ifHas +import ru.dbotthepony.mc.otm.core.nbt.map import java.math.BigInteger import java.util.* @@ -88,7 +88,7 @@ class PortableCondensationDriveItem(capacity: Int) : fun getFilterSettings(drive: ItemStack): ItemFilter { val filter = ItemFilter(MAX_FILTERS) filter.isWhitelist = true - drive.tag?.ifHas(FILTER_PATH, CompoundTag::class.java, filter::deserializeNBT) + drive.tag?.map(FILTER_PATH, filter::deserializeNBT) return filter } 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 966bbbbff..f04e9f742 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 @@ -4,21 +4,26 @@ import net.minecraft.network.FriendlyByteBuf 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.Slot import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.PacketDistributor import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.block.entity.matter.MatterPanelBlockEntity import ru.dbotthepony.mc.otm.capability.matter.* import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.addSorted +import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec +import ru.dbotthepony.mc.otm.core.util.EnumValueCodec +import ru.dbotthepony.mc.otm.core.util.ItemSorter import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphListener import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.network.* import ru.dbotthepony.mc.otm.registry.MMenus import java.util.* -import java.util.function.Consumer import java.util.function.Supplier +import kotlin.Comparator +import kotlin.collections.ArrayList class CancelTaskPacket(val id: UUID) : MatteryPacket { override fun write(buff: FriendlyByteBuf) { @@ -146,66 +151,70 @@ class MatterPanelMenu @JvmOverloads constructor( sendNetwork(TasksChangePacket(false, listOf(task))) } - // client code + val sorting: ItemSorter by mSynchronizer.ComputedField( + getter = { tile?.getPlayerSettings(ply)?.sorter ?: ItemSorter.DEFAULT }, + codec = EnumValueCodec(ItemSorter::class.java), + observer = { + patterns.sortWith(actualComparator) + tasks.sortWith(actualTaskComparator) + }) + + val isAscending: Boolean by mSynchronizer.ComputedField( + getter = { tile?.getPlayerSettings(ply)?.ascending ?: true }, + codec = BooleanValueCodec, + observer = { + patterns.sortWith(actualComparator) + tasks.sortWith(actualTaskComparator) + }) + + val changeIsAscending = booleanInput(allowSpectators = true) { tile?.getPlayerSettings(ply)?.ascending = it } + val changeSorting = PlayerInput(EnumValueCodec(ItemSorter::class.java), allowSpectators = true) { tile?.getPlayerSettings(ply)?.sorter = it } + + 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) } + val patterns = ArrayList() val tasks = ArrayList>() - var changeset = 0 fun networkPatternsUpdated(patterns: Collection) { - changeset++ - for (pattern in patterns) { val index = this.patterns.indexOfFirst(pattern::matchId) if (index != -1) { this.patterns[index] = pattern } else { - this.patterns.add(pattern) + this.patterns.addSorted(pattern, actualComparator) } } } fun networkPatternsRemoved(patterns: Collection) { - changeset++ - for (pattern in patterns) { this.patterns.remove(pattern) } } fun networkTasksUpdated(tasks: Collection>) { - changeset++ - for (task in tasks) { val index = this.tasks.indexOfFirst(task::matchId) if (index != -1) { this.tasks[index] = task } else { - this.tasks.add(task) + this.tasks.addSorted(task, actualTaskComparator) } - - updateWatcher?.accept(task) } } fun networkTasksRemoved(tasks: Collection>) { - changeset++ - for (task in tasks) { this.tasks.remove(task) - deleteWatcher?.accept(task) } } - private var deleteWatcher: Consumer>? = null - private var updateWatcher: Consumer>? = null - - fun networkTaskWatcher(updateWatcher: Consumer>, deleteWatcher: Consumer>) { - this.deleteWatcher = deleteWatcher - this.updateWatcher = updateWatcher - } - // server code fun requestReplication(ply: ServerPlayer, id: UUID, count: Int) { if (ply.isSpectator) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt index eb573c63f..3b250b558 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt @@ -688,6 +688,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa private val getter: () -> V, private val codec: IStreamCodec, name: String = nextFieldName(), + private val observer: (new: V) -> Unit = {} ) : AbstractField(name), IField { private var remote: V? = null private var clientValue: V? = null @@ -726,7 +727,9 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa } override fun read(stream: DataInputStream) { - clientValue = codec.read(stream) + val newValue = codec.read(stream) + clientValue = newValue + observer.invoke(newValue) } } 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 415c331dc..a8139397e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt @@ -5,6 +5,7 @@ import net.minecraft.world.item.CreativeModeTab import net.minecraft.world.item.ItemStack import net.minecraftforge.event.CreativeModeTabEvent import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.core.util.CreativeMenuComparator import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.util.WriteOnce import ru.dbotthepony.mc.otm.registry.MItems.BATTERY_CREATIVE @@ -16,6 +17,8 @@ object MCreativeTabs { private set fun register(event: CreativeModeTabEvent.Register) { + CreativeMenuComparator.invalidate() + MAIN = event.registerCreativeModeTab(ResourceLocation(OverdriveThatMatters.MOD_ID, "main")) { it.icon { ItemStack(BATTERY_CREATIVE, 1) } it.title(TranslatableComponent("itemGroup.otm")) diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.png index 9346529e2d65a83ff9af3015c31308c15028e4ed..9905a78ace4c217229c6ee8f66bd03428dea1164 100644 GIT binary patch delta 768 zcmV+b1ONQT1)~O#7YZZ@1^@s634krLks%y^Kpe$i(@I4v4t6NwkfAzR5S8L6RV;#q z(pG5I!Q|2}XktiGTpR`0f`cE6RR z_@99*t>fQl0yCeaH#%DM2pHT3F0MP8ya!zF0FftMG9*Xx(-cZ2;QfrgsQ?V!0)4A) zZ|!}YJ^(rDYUu_zI0VMZl)dKh?%wX+{yo#~?+1Zxa;^GU+JFE600v@9M??UB00000 z0Qp0^e*gdg32;bRa{vGf6951U69E94oEQKA00(qQO+^Ri0|gB`9G8GATmS$8AW1|) zRA}DqnqdxuFbIXM@mL(6d+9F{LbL-`x~cC=mNcf8@}NX<%K`uZ<6-8VnfHHL=dP@? zf^{~A&_yCg#g&vf7 zi*2UJ+Bp#?_hFOAds-uFrK3gbS=PB< z1Zn1JNDG~b^xK3U_QG=Z{0jg8000000JCuSUNbK&w!!{pp-aE8=tjE;|MrixWqfKW yBBKF>ex%T-sW-@I{g(h6IJ8{>00000DmVaxk3T`%wah610000+V2Rn#}WQa}{L`58>ibb$c z+6t{Yn7s54nlvOSE{=k0!NH%!s)LKOt`4q(Aov5~>f)s6A|>9J6k5c1;qgAsyXWxU zeSpxYFwN?k05sh;Q}LLX&8><(uLxiSVMvft?1$8VgAE(<&}VrEkF#1Ue#*uhE% zvy!P1PZLY3rc=I<^;qS+#aXM=SnHnrg`vE@lIA+CVZ^b71d~sYx#>6a%_n9Oq*c=-CCDb;tQWcAVx3 z5PSx%^tQj+0A@c)ueUYn2ncTj7uRh~*#j><;QfrgDF+PP0)1=V z+*;>2eE>2vtK}Qu;1C!qQueyfyL&t5_HR#Xem}UQa>E3@&nN%@00v@9M??UB{{a60 z|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^Ri0UrcA4Y!5Vx&QzG#7RU! zR9M69m)#D4AP9xC`B*$Y?^RcB%pqw|_H(|i%{Hym0SuP~0081)=8c&*IN>A10$&o! zdm>6xj4nc?0IfPKf%WssTse+^(l)xjUTbN}SDLJuqKW%M_NOZ%-y7}TtUL;aC$l3G zY3d$*WX_0Ctunk@$K9->2KCM%q9iX*Hhi}0u#E8Tsb@@<@6^U`2-<3uY4o4E?#+99 zbXgUx@e|Gm*7I`f!)hTTCTnwAUB_QYQ4Ki`EKYxL1P1_sY3z@Uy-ObtdhYE20000< KMNUMnLSTX@{4^Z^ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/storage_controls.xcf index 2e3d11ece63bb479b22eae7e67d46b6a2ed8e80f..54ab55958412f706096ff5974c650784cd950eec 100644 GIT binary patch literal 4408 zcmdT{&1+Ow6uK9q1t(|b+;i?Z_uia)?r+Y$ zX|=B`h1X{0!|%o$4FUQFJVpid3-}|j=b@F^}XHOJr z^R1c1g~jEtrbKe8=?BpeXYCHoN0GjVPhP<>peZwsejvPuY~Q+ z%;mW*XDrUocjs0@HQv#++B=vrJLLVQJE_-NW^ZVjD`}a3t7YkomX()U_OwhsnYqZt z5996d>$4od9FwwO=2e1^Q9r$k7GuG*b6(!)5aM%3KkDcOM-Lsn=;(}_Ym&-ft5>1( z@7Y)bH4}B3ZIN^fM=| z@gh)B5q~NaX^dpVj_O6ekWsLot7n`ad01*sL`&iTttqj0g+7fk>|OOF_Aasae|Pna z^Ck8!_k>(xT5X<*(u%5V@pEADNphRsVL~F~VYGuHk@Fl1?l>Kr1%t%kQ6C>n!KSr0 zg@?N4Mdys9-W31Qiu&S7te|9n};lR8$xD(Q# znByDUq4H&?W3yy1SH9E72UGchwNC&o6_JWcs)BgYj)(v#g)I**asaIP3RDu$TYV+) zDsXv14CO+2Or#{X1~hqc2Z+KKKN~y_G-GQ(pr=rXt*JYT_m#^NOqIxU)r$_;0*tXBWU({Yd3J|cP#Hf-%Z2nk4oO>7NtO#!x^oDhEl5%ou? literal 3189 zcmd5;&ubGw6rLpABu!dd>yML#)mCVQKq6T1Py|oCSnwoxsNJSX475o}sOnkJ zJa`a9Ja`k4LLpR}A}r!@a?^jMv>m^1cSokO)e=1HOTT$<-kW)y_vY=}tyCA6?3H5K zewZ&51n4d>L;>^)I1R*N7^pl8OvN!wa2Vw<#NiB(zWG830>pJaKpHq7$nYgFbOJMaAp<1o*K zmTKA@i0|a4Y|de0cKhO7b)LZ z!{}$8dV1~!D3dM$TE1Y&8AG-WIcvy$hTLz+1CV*sQvfLi@PE+%T?r#0N?%v2xa<{XFaTD#w zD?Yhpch>R6v4$?xf~gJDcJyjemqWKTna~b=o(=2&K}}|_8*6ofp~~LY`pMv(!1sd* zeuKoldSyYzP>?>-;CsSFQKExc_+&}Ar=Xe?k#yv}xcCw?!4DIJ7F1?TAv6yNP3^&k PKZJ(wn1n)TYEt|HFMOI3 diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ComparatorTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ComparatorTests.kt new file mode 100644 index 000000000..1a943112e --- /dev/null +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ComparatorTests.kt @@ -0,0 +1,34 @@ +package ru.dbotthepony.mc.otm.tests + +import it.unimi.dsi.fastutil.ints.IntComparators +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import ru.dbotthepony.mc.otm.core.addSorted +import java.util.Random + +object ComparatorTests { + @Test + @DisplayName("Comparator tests") + fun test() { + val sortedList = mutableListOf(1, 4, 6) + sortedList.addSorted(2, IntComparators.NATURAL_COMPARATOR) + sortedList.addSorted(3, IntComparators.NATURAL_COMPARATOR) + sortedList.addSorted(7, IntComparators.NATURAL_COMPARATOR) + sortedList.addSorted(-1, IntComparators.NATURAL_COMPARATOR) + + assertEquals(mutableListOf(-1, 1, 2, 3, 4, 6, 7), sortedList) + + val rand = Random() + val sorted2 = ArrayList() + + for (i in 0 .. 100) { + sorted2.addSorted(rand.nextInt(-100, 100), IntComparators.NATURAL_COMPARATOR) + } + + val sorted22 = ArrayList(sorted2) + sorted22.sort() + + assertEquals(sorted22, sorted2) + } +}