From 9dcf24cae76d27f94a81cc2c9f6a7549afbfa009 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Wed, 5 Jul 2023 17:06:23 +0700 Subject: [PATCH] Upgrade system, conditional quickmove slots, frame panel close button --- .../mc/otm/datagen/lang/English.kt | 33 +++ .../mc/otm/datagen/lang/Russian.kt | 33 +++ .../dbotthepony/mc/otm/datagen/tags/Tags.kt | 2 + .../mc/otm/capability/MatteryCapability.java | 5 + .../dbotthepony/mc/otm/block/entity/Jobs.kt | 21 +- .../block/entity/MatteryWorkerBlockEntity.kt | 8 +- .../entity/tech/PlatePressBlockEntity.kt | 7 +- .../dbotthepony/mc/otm/capability/Upgrades.kt | 200 ++++++++++++++++++ .../mc/otm/client/render/Widgets18.kt | 4 +- .../mc/otm/client/screen/MatteryScreen.kt | 5 +- .../otm/client/screen/panels/EditablePanel.kt | 29 ++- .../mc/otm/client/screen/panels/FramePanel.kt | 69 +++++- .../client/screen/panels/button/Buttons.kt | 99 ++++++++- .../screen/panels/input/QueryUserPanel.kt | 1 + .../screen/tech/AndroidStationScreen.kt | 2 +- .../client/screen/tech/PlatePressScreen.kt | 3 +- .../screen/tech/TwinPlatePressScreen.kt | 2 +- .../mc/otm/config/MachinesConfig.kt | 31 +++ .../mc/otm/container/UpgradeContainer.kt | 54 +++++ .../kotlin/ru/dbotthepony/mc/otm/core/Ext.kt | 4 +- .../mc/otm/core/collect/ConditionalEnumSet.kt | 41 ++++ .../mc/otm/core/collect/ConditionalSet.kt | 85 ++++---- .../dbotthepony/mc/otm/item/SimpleUpgrade.kt | 51 +++++ .../ru/dbotthepony/mc/otm/menu/MatteryMenu.kt | 90 +++++++- .../ru/dbotthepony/mc/otm/menu/Slots.kt | 1 + .../mc/otm/menu/tech/PlatePressMenu.kt | 2 + .../mc/otm/menu/tech/TwinPlatePressMenu.kt | 2 + .../mc/otm/registry/MCreativeTabs.kt | 1 + .../ru/dbotthepony/mc/otm/registry/MItems.kt | 35 ++- .../ru/dbotthepony/mc/otm/registry/Tags.kt | 1 + .../textures/gui/widgets/misc.png | Bin 468 -> 1327 bytes .../textures/gui/widgets/side_controls.png | Bin 2775 -> 2953 bytes .../textures/gui/widgets/side_controls.xcf | Bin 72882 -> 73073 bytes 33 files changed, 840 insertions(+), 81 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/capability/Upgrades.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalEnumSet.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.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 3fce11216..e76ae4983 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 @@ -603,6 +603,18 @@ private fun items(provider: MatteryLanguageProvider) { add(MItems.ZPM_BATTERY, "Zero Point Module") add(MItems.ZPM_BATTERY, "description", "Can be found in hands of those who travel between dimensions, if they ever reached different reality of origin of these constructs...") + + add(MItems.CreativeUpgrades.SPEED, "Creative Speed Upgrade") + add(MItems.CreativeUpgrades.ENERGY_CONSUMPTION, "Creative Energy Consumption Upgrade") + add(MItems.CreativeUpgrades.ENERGY_STORAGE, "Creative Energy Storage Upgrade") + add(MItems.CreativeUpgrades.ENERGY_STORAGE_FLAT, "Creative Energy Storage Upgrade (Flat)") + add(MItems.CreativeUpgrades.ENERGY_STORAGE_FLAT_SMALL, "Creative Energy Storage Upgrade (Flat Small)") + add(MItems.CreativeUpgrades.ENERGY_THROUGHPUT, "Creative Energy Throughput Upgrade") + add(MItems.CreativeUpgrades.ENERGY_THROUGHPUT_FLAT, "Creative Energy Throughput Upgrade (Flat)") + add(MItems.CreativeUpgrades.ENERGY_THROUGHPUT_FLAT_SMALL, "Creative Energy Throughput Upgrade (Flat Small)") + add(MItems.CreativeUpgrades.FAILSAFE, "Creative Failsafe Upgrade") + add(MItems.CreativeUpgrades.FAILURE, "Creative Failure Upgrade") + add(MItems.CreativeUpgrades.PROCESSING_ITEMS, "Creative Item Processing Upgrade") } } @@ -688,6 +700,27 @@ private fun gui(provider: MatteryLanguageProvider) { gui("side_mode.pull", "Pull") gui("side_mode.push", "Push") + gui("upgrades", "Upgrades") + gui("upgrades.current", "Active upgrades:") + gui("upgrade.speed", "Operation speed: %s%%") + gui("upgrade.processing_items", "Items processed per cycle: +%s") + gui("upgrade.energy_storage_flat", "Energy capacity: +%s") + gui("upgrade.energy_storage", "Energy capacity: +%s%%") + gui("upgrade.energy_consumed", "Energy consumption: %s%%") + gui("upgrade.energy_throughput_flat", "Energy throughput: +%s") + gui("upgrade.energy_throughput", "Energy throughput: +%s%%") + gui("upgrade.failsafe", "Failure chance: %s%%") + + gui("upgrade_type.list", "Upgrade classification:") + gui("upgrade_type.allowed", "Allowed upgrades:") + gui("upgrade_type.allowed_none", "No possible upgrades at the moment.") + gui("upgrade_type.speed", "Speed") + gui("upgrade_type.processing", "Processing") + gui("upgrade_type.energy_storage", "Energy Storage") + gui("upgrade_type.energy_consumption", "Energy Consumption") + gui("upgrade_type.energy_throughput", "Energy Throughput") + gui("upgrade_type.failsafe", "Failsafe") + gui("balance_inputs", "Balance input slots") gui("sorting.default", "Default sorting") 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 14b993b9e..291b4ff5a 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 @@ -607,6 +607,18 @@ private fun items(provider: MatteryLanguageProvider) { add(MItems.ZPM_BATTERY, "Модуль нулевой точки") add(MItems.ZPM_BATTERY, "description", "Может быть найден у тех, кто путешествует между измерениями, если, конечно, они смогли достигнуть вселенной, где данные устройства были созиданы...") + + add(MItems.CreativeUpgrades.SPEED, "Творческое улучшение скорости") + add(MItems.CreativeUpgrades.ENERGY_CONSUMPTION, "Творческое улучшение энергоэффективности") + add(MItems.CreativeUpgrades.ENERGY_STORAGE, "Творческое улучшение энергохраналища") + add(MItems.CreativeUpgrades.ENERGY_STORAGE_FLAT, "Творческое улучшение энергохраналища (простое)") + add(MItems.CreativeUpgrades.ENERGY_STORAGE_FLAT_SMALL, "Творческое улучшение энергохраналища (малое простое)") + add(MItems.CreativeUpgrades.ENERGY_THROUGHPUT, "Творческое улучшение энергоканала") + add(MItems.CreativeUpgrades.ENERGY_THROUGHPUT_FLAT, "Творческое улучшение энергоканала (простое)") + add(MItems.CreativeUpgrades.ENERGY_THROUGHPUT_FLAT_SMALL, "Творческое улучшение энергоканала (малое простое)") + add(MItems.CreativeUpgrades.FAILSAFE, "Творческое улучшение отказоустойчивости") + add(MItems.CreativeUpgrades.FAILURE, "Творческое улучшение краха") + add(MItems.CreativeUpgrades.PROCESSING_ITEMS, "Творческое улучшение обработки") } } @@ -692,6 +704,27 @@ private fun gui(provider: MatteryLanguageProvider) { gui("side_mode.pull", "Автоматическое вытягивание") gui("side_mode.push", "Автоматическое выталкивание") + gui("upgrades", "Улучшения") + gui("upgrades.current", "Активные улучшения:") + gui("upgrade.speed", "Скорость работы: %s%%") + gui("upgrade.processing_items", "Работы за цикл: +%s") + gui("upgrade.energy_storage_flat", "Хранилище энергии: +%s") + gui("upgrade.energy_storage", "Хранилище энергии: +%s%%") + gui("upgrade.energy_consumed", "Потребление энергии: %s%%") + gui("upgrade.energy_throughput_flat", "Пропускная способность энергии: +%s") + gui("upgrade.energy_throughput", "Пропускная способность энергии: +%s%%") + gui("upgrade.failsafe", "Шанс неудачи: %s%%") + + gui("upgrade_type.list", "Классификация улучшения:") + gui("upgrade_type.allowed", "Допустимые улучшения:") + gui("upgrade_type.allowed_none", "На данный момент нет допустимых улучшений.") + gui("upgrade_type.speed", "Скорость") + gui("upgrade_type.processing", "Обработка") + gui("upgrade_type.energy_storage", "Энергохранилище") + gui("upgrade_type.energy_consumption", "Энергоэффективность") + gui("upgrade_type.energy_throughput", "Энергоканал") + gui("upgrade_type.failsafe", "Отказоустойчивость") + gui("balance_inputs", "Балансировать входные слоты") gui("sorting.default", "Сортировка по умолчанию") diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt index 9932f8709..b69231e57 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/tags/Tags.kt @@ -112,6 +112,8 @@ fun addTags(tagsProvider: TagsProvider) { tagsProvider.items.forge("shears").add(MItems.TRITANIUM_SHEARS) tagsProvider.items.forge("shields").add(MItems.TRITANIUM_SHIELD) + tagsProvider.items.Appender(MItemTags.UPGRADES).add(MItems.CreativeUpgrades.LIST) + tagsProvider.blocks.Appender(BlockTags.STAIRS) .add(MRegistry.FLOOR_TILES_STAIRS.blocks.values) .add(MRegistry.TRITANIUM_STAIRS.allBlocks.values) diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java index ad3d8aeff..37a8aba97 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java @@ -63,6 +63,10 @@ public class MatteryCapability { @NotNull public static final Capability CURIOS_ITEM = CapabilityManager.get(new CapabilityToken<>() {}); + @Nonnull + @NotNull + public static final Capability UPGRADE = CapabilityManager.get(new CapabilityToken<>() {}); + public static void register(RegisterCapabilitiesEvent event) { event.register(IMatteryEnergyStorage.class); event.register(MatteryPlayerCapability.class); @@ -72,5 +76,6 @@ public class MatteryCapability { event.register(IReplicationTaskProvider.class); event.register(IMatteryDrive.class); event.register(StorageNode.class); + event.register(IMatteryUpgrade.class); } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt index 3ebeaff46..7a595a8cd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt @@ -3,7 +3,9 @@ package ru.dbotthepony.mc.otm.block.entity import net.minecraft.nbt.CompoundTag import net.minecraft.world.item.ItemStack import net.minecraftforge.common.util.INBTSerializable +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage +import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.getDecimal import ru.dbotthepony.mc.otm.core.math.set @@ -153,6 +155,7 @@ abstract class MachineJobEventLoop : INBTSerializable : INBTSerializable : INBTSerializable( blockPos: BlockPos, blockState: BlockState, val jobDeserializer: (tag: CompoundTag) -> JobType?, - maxJobs: Int = 1 + maxJobs: Int = 1, ) : MatteryPoweredBlockEntity(type, blockPos, blockState) { val jobEventLoops: ImmutableList> = immutableList(maxJobs) { id -> object : MachineJobEventLoop() { @@ -38,6 +40,8 @@ abstract class MatteryWorkerBlockEntity( get() = matteryEnergy override val isBlockedByRedstone: Boolean get() = redstoneControl.isBlockedByRedstone + override val upgrades: IMatteryUpgrade? + get() = this@MatteryWorkerBlockEntity.upgrades override fun deserializeJob(nbt: CompoundTag): JobType? { return jobDeserializer.invoke(nbt) @@ -61,6 +65,8 @@ abstract class MatteryWorkerBlockEntity( } } + open val upgrades: IMatteryUpgrade? get() = null + var balanceInputs = false init { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt index e88dbb13f..e5737178a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt @@ -12,11 +12,14 @@ import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus import ru.dbotthepony.mc.otm.block.entity.MachineItemJob import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade +import ru.dbotthepony.mc.otm.capability.UpgradeType import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.HandlerFilter +import ru.dbotthepony.mc.otm.container.UpgradeContainer import ru.dbotthepony.mc.otm.container.balance import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu @@ -29,7 +32,8 @@ class PlatePressBlockEntity( p_155230_: BlockState, val isTwin: Boolean = false, ) : MatteryWorkerBlockEntity(if (isTwin) MBlockEntities.TWIN_PLATE_PRESS else MBlockEntities.PLATE_PRESS, p_155229_, p_155230_, ::MachineItemJob, if (isTwin) 2 else 1) { - val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::setChangedLight, MachinesConfig.PLATE_PRESS)) + override val upgrades = UpgradeContainer(this::setChangedLight, if (isTwin) 4 else 3, UpgradeType.BASIC) + val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::setChangedLight, upgrades.transform(MachinesConfig.PLATE_PRESS))) val inputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer) val outputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer) @@ -55,6 +59,7 @@ class PlatePressBlockEntity( savetable(::inputContainer) savetable(::outputContainer) savetables.double(::experience) + savetables.stateful(::upgrades) } override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Upgrades.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Upgrades.kt new file mode 100644 index 000000000..67040f3b0 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Upgrades.kt @@ -0,0 +1,200 @@ +package ru.dbotthepony.mc.otm.capability + +import net.minecraft.ChatFormatting +import net.minecraft.network.chat.Component +import ru.dbotthepony.mc.otm.client.ShiftPressedCond +import ru.dbotthepony.mc.otm.config.MachinesConfig +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.immutableSet +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.util.formatPower + +/** + * Upgrades merge by sum of their effects + * + * Values which are otherwise not stated to allow negatives or not, Do allow negative values. + */ +interface IMatteryUpgrade { + /** + * Type(s) this upgrade represent + */ + val upgradeTypes: Set get() = setOf() + + /** + * [speedBonus] of `1.0` means that machine doubles the speed (performs twice the amount of + * work ticks per work cycle). + */ + val speedBonus: Double get() = 0.0 + + /** + * **CAN NOT be negative.** + * + * [processingItems] of `4` means machine will accept at most 5 (1 + 4) of items per job. + */ + val processingItems: Int get() = 0 + + /** + * **CAM NOT be negative.** + * + * Added directly over regular energy storage capacity. + */ + val energyStorageFlat: Decimal get() = Decimal.ZERO + + /** + * **CAM NOT be negative.** + */ + val energyStorage: Decimal get() = Decimal.ZERO + + /** + * Value of `1` means power consumption is doubled, `2` is tripled, `-0.5` is halved. + */ + val energyConsumed: Decimal get() = Decimal.ZERO + + /** + * **CAN NOT be negative.** + * + * Added directly over regular throughput + */ + val energyThroughputFlat: Decimal get() = Decimal.ZERO + + /** + * **CAN NOT be negative.** + * + * Value of `1` means power throughput is doubled, `2` is tripled, and so on. + */ + val energyThroughput: Decimal get() = Decimal.ZERO + + /** + * **CAN NOT be negative.** + * + * **Merged by multiplication** + */ + val failureMultiplier: Double get() = 1.0 + + companion object : IMatteryUpgrade +} + +fun IMatteryUpgrade.addUpgradeTooltipLines(tooltips: MutableCollection) { + if (upgradeTypes.isNotEmpty() && ShiftPressedCond.asBoolean) { + tooltips.add(TranslatableComponent("otm.gui.upgrade_type.list").withStyle(ChatFormatting.GRAY)) + + for (upgrade in upgradeTypes) { + tooltips.add(upgrade.component.copy().withStyle(ChatFormatting.GRAY)) + } + + tooltips.add(TextComponent("")) + } + + if (speedBonus >= 0.01) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.speed", TextComponent("+" + (speedBonus.coerceIn(MachinesConfig.Upgrades.MIN_SPEED, MachinesConfig.Upgrades.MAX_SPEED) * 100.0).toInt().toString()).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } else if (speedBonus <= -0.01) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.speed", TextComponent((speedBonus.coerceIn(MachinesConfig.Upgrades.MIN_SPEED, MachinesConfig.Upgrades.MAX_SPEED) * 100.0).toInt().toString()).withStyle(ChatFormatting.DARK_RED)).withStyle(ChatFormatting.GRAY)) + } + + if (processingItems != 0) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.processing_items", TextComponent("+$processingItems").withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyStorageFlat != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_storage_flat", energyStorageFlat.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyStorage != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_storage", TextComponent((energyStorage * 100).toString(0)).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyConsumed > Decimal.ONE) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_consumed", TextComponent("+" + (energyConsumed.coerceIn(MachinesConfig.Upgrades.MIN_ENERGY, MachinesConfig.Upgrades.MAX_ENERGY) * 100).toString(0)).withStyle(ChatFormatting.DARK_RED)).withStyle(ChatFormatting.GRAY)) + } else if (energyConsumed < Decimal.MINUS_ONE) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_consumed", TextComponent((energyConsumed.coerceIn(MachinesConfig.Upgrades.MIN_ENERGY, MachinesConfig.Upgrades.MAX_ENERGY) * 100).toString(0)).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyThroughputFlat != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_throughput_flat", energyThroughputFlat.formatPower(formatAsReadable = ShiftPressedCond).copy().withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (energyThroughput != Decimal.ZERO) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.energy_throughput", TextComponent((energyThroughput * 100).toString(0)).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } + + if (failureMultiplier >= 1.01) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.failsafe", TextComponent("+" + ((failureMultiplier - 1) * 100).toInt().toString()).withStyle(ChatFormatting.DARK_RED)).withStyle(ChatFormatting.GRAY)) + } else if (failureMultiplier <= 0.99) { + tooltips.add(TranslatableComponent("otm.gui.upgrade.failsafe", TextComponent(((failureMultiplier.coerceAtLeast(0.0) - 1) * 100).toInt().toString()).withStyle(ChatFormatting.DARK_GREEN)).withStyle(ChatFormatting.GRAY)) + } +} + +fun IMatteryUpgrade.getUpgradeTooltipLines(): MutableList { + return ArrayList().also { addUpgradeTooltipLines(it) } +} + +enum class UpgradeType { + SPEED, + PROCESSING, + ENERGY_STORAGE, + ENERGY_CONSUMPTION, + ENERGY_THROUGHPUT, + FAILSAFE; + + val localeId = "otm.gui.upgrade_type.${name.lowercase()}" + val component: Component get() = TranslatableComponent(localeId) + + val flag = 1 shl (ordinal + 1) + fun set() = sets[flag] + + companion object { + private val cached = values() + + private val sets = Array(2 shl cached.size) { + immutableSet { + for (u in values()) if (it.and(u.flag) != 0) accept(u) + } + } + + @JvmField + val ALL = set(*values()) + @JvmField + val BASIC = set(SPEED, ENERGY_STORAGE, ENERGY_CONSUMPTION, ENERGY_THROUGHPUT) + + fun set(vararg types: UpgradeType): Set { + var flags = 0 + for (v in types) flags = flags or v.flag + return sets[flags] + } + + fun set(): Set { + return sets[0] + } + + fun set( + type0: UpgradeType, + ): Set = sets[type0.flag] + + fun set( + type0: UpgradeType, + type1: UpgradeType, + ): Set = sets[type0.flag or type1.flag] + + fun set( + type0: UpgradeType, + type1: UpgradeType, + type2: UpgradeType, + ): Set = sets[type0.flag or type1.flag or type2.flag] + + fun set( + type0: UpgradeType, + type1: UpgradeType, + type2: UpgradeType, + type3: UpgradeType, + ): Set = sets[type0.flag or type1.flag or type2.flag or type3.flag] + + fun set( + type0: UpgradeType, + type1: UpgradeType, + type2: UpgradeType, + type3: UpgradeType, + type4: UpgradeType, + ): Set = sets[type0.flag or type1.flag or type2.flag or type3.flag or type4.flag] + } +} 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 d347c5feb..71f858864 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 @@ -59,7 +59,7 @@ object Widgets18 { val PATTERN_SLOT_BACKGROUND = slotBgGrid.next() val MATTER_CAPACITOR_SLOT_BACKGROUND = slotBgGrid.next() - private val controlsGrid = WidgetLocation.SIDE_CONTROLS.grid(rows = 7, columns = 9) + private val controlsGrid = WidgetLocation.SIDE_CONTROLS.grid(rows = 8, columns = 9) val BATTERY_ONLY = controlsGrid.next() val ITEMS_CONFIGURATION = controlsGrid.next() @@ -160,4 +160,6 @@ object Widgets18 { put(RelativeSide.FRONT, FRONT_CONTROLS) put(RelativeSide.BACK, BACK_CONTROLS) } + + val UPGRADES = controlsGrid.next() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt index 1d5bd9dd1..468a71ba5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/MatteryScreen.kt @@ -319,7 +319,10 @@ abstract class MatteryScreen(menu: T, inventory: Inventory, tit * @return FramePanel created, or null */ protected open fun makeMainFrame(): FramePanel>? { - return FramePanel(this, null, 0f, 0f, DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, getTitle()) + return FramePanel(this, null, 0f, 0f, DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, getTitle()).also { + it.onClose { onClose() } + it.makeCloseButton() + } } public override fun recalculateQuickCraftRemaining() { 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 a0ca9d7c8..c188af33e 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 @@ -1,8 +1,10 @@ package ru.dbotthepony.mc.otm.client.screen.panels import com.google.common.collect.ImmutableList +import com.mojang.blaze3d.platform.InputConstants import com.mojang.blaze3d.systems.RenderSystem import com.mojang.blaze3d.vertex.PoseStack +import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import it.unimi.dsi.fastutil.objects.ReferenceArraySet import net.minecraft.client.gui.ComponentPath import net.minecraft.client.gui.Font @@ -23,6 +25,7 @@ import ru.dbotthepony.mc.otm.client.render.currentScissorRect import ru.dbotthepony.mc.otm.client.render.popScissorRect import ru.dbotthepony.mc.otm.client.render.pushScissorRect import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.button.AbstractButtonPanel import ru.dbotthepony.mc.otm.client.screen.panels.input.QueryUserPanel import java.util.* import kotlin.collections.ArrayList @@ -417,6 +420,7 @@ open class EditablePanel @JvmOverloads constructor( return field } + field = null return null } @@ -965,7 +969,7 @@ open class EditablePanel @JvmOverloads constructor( return true } - if (isHovered) { + if (isHovered || this is AbstractButtonPanel<*> && isPressed) { val tooltip = tooltip val tooltipList = tooltipList @@ -1186,10 +1190,6 @@ open class EditablePanel @JvmOverloads constructor( this.height = height } - companion object { - private val LOGGER = LogManager.getLogger()!! - } - protected open fun visibilityChanges(new: Boolean, old: Boolean) { } @@ -1589,7 +1589,7 @@ open class EditablePanel @JvmOverloads constructor( fun keyPressed(key: Int, scancode: Int, mods: Int): Boolean { if (!isVisible() || !acceptKeyboardInput) return false if (!isEverFocused()) return false - if (flashAnyBlocker(true)) return true + if (flashAnyBlocker(key !in ignoreFlashKeys)) return true if (isFocusedThis) return keyPressedInternal(key, scancode, mods) for (child in visibleChildrenInternal) { @@ -1737,4 +1737,21 @@ open class EditablePanel @JvmOverloads constructor( fun queryUser(title: Component, text: Collection, onConfirm: Runnable, onCancel: Runnable? = null): QueryUserPanel { return QueryUserPanel(screen, title, text, onConfirm, onCancel).also { blockingWindow = it } } + + companion object { + private val LOGGER = LogManager.getLogger()!! + + private val ignoreFlashKeys = IntAVLTreeSet() + + init { + ignoreFlashKeys.add(InputConstants.KEY_LEFT) + ignoreFlashKeys.add(InputConstants.KEY_RIGHT) + ignoreFlashKeys.add(InputConstants.KEY_UP) + ignoreFlashKeys.add(InputConstants.KEY_DOWN) + ignoreFlashKeys.add(InputConstants.KEY_RCONTROL) + ignoreFlashKeys.add(InputConstants.KEY_LCONTROL) + ignoreFlashKeys.add(InputConstants.KEY_RSHIFT) + ignoreFlashKeys.add(InputConstants.KEY_LSHIFT) + } + } } 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 d1f9e24cb..ace546c82 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 @@ -12,6 +12,8 @@ import net.minecraft.network.chat.Component import org.lwjgl.opengl.GL30 import ru.dbotthepony.mc.otm.client.playGuiClickSound import ru.dbotthepony.mc.otm.client.render.* +import ru.dbotthepony.mc.otm.client.screen.panels.button.AbstractButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.button.ButtonPanel open class FramePanel( screen: S, @@ -123,6 +125,30 @@ open class FramePanel( } } + inner class CloseButton : AbstractButtonPanel(screen, this@FramePanel, this@FramePanel.width - CLOSE_BUTTON.width, 0f, CLOSE_BUTTON.width, CLOSE_BUTTON.height) { + override fun onClick(mouseButton: Int) { + close() + } + + override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (isPressed) { + CLOSE_BUTTON_PRESSED.render(graphics, 0f, 0f, width, height) + } else if (isHovered) { + CLOSE_BUTTON_HOVERED.render(graphics, 0f, 0f, width, height) + } else { + CLOSE_BUTTON.render(graphics, 0f, 0f, width, height) + } + } + + override fun onRemoved() { + super.onRemoved() + + if (closeButton == this) { + closeButton = null + } + } + } + protected val tabs: java.util.ArrayList = ArrayList() override fun performLayout() { @@ -131,13 +157,29 @@ open class FramePanel( tab.initial = i == 0 } + closeButton?.setPos(width - CLOSE_BUTTON.width, 0f) + super.performLayout() } protected var dragging = false + protected var closeButton: CloseButton? = null var closeOnEscape = false + fun makeCloseButton() { + if (closeButton == null) + closeButton = CloseButton() + } + + /** + * Adds close button and makes panel [close] when user presses ESC with this panel focused + */ + fun behaveAsWindow() { + closeOnEscape = true + makeCloseButton() + } + init { setDockPadding(PADDING, if (title != null) PADDING_TOP else PADDING, PADDING, PADDING) } @@ -187,9 +229,24 @@ open class FramePanel( } + protected val closeCallbacks = ArrayList() + + fun onClose(callback: Runnable) { + closeCallbacks.add(callback) + } + + /** + * Usually just calls [remove] + */ + open fun close() { + if (isRemoved) return + remove() + closeCallbacks.forEach { it.run() } + } + override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean { if (key == InputConstants.KEY_ESCAPE && closeOnEscape) { - remove() + close() return true } @@ -258,9 +315,13 @@ open class FramePanel( padding = DockProperty(-3f, -3f, -3f, -3f) ) - val TAB_RIGHT_CONNECTION = WidgetLocation.MISC.sprite(x = 30f, y = 0f, width = 3f, height = 5f) - val TAB_LEFT_CONNECTION = WidgetLocation.MISC.sprite(x = 33f, y = 0f, width = 3f, height = 5f) - val TAB_BACKGROUND = WidgetLocation.MISC.sprite(x = 30f, y = 6f, width = 6f, height = 6f) + val TAB_RIGHT_CONNECTION = WidgetLocation.MISC.sprite(x = 30f, y = 0f, width = 3f, height = 5f) + val TAB_LEFT_CONNECTION = WidgetLocation.MISC.sprite(x = 33f, y = 0f, width = 3f, height = 5f) + val TAB_BACKGROUND = WidgetLocation.MISC.sprite(x = 30f, y = 6f, width = 6f, height = 6f) + + val CLOSE_BUTTON = WidgetLocation.MISC.sprite(x = 51f, y = 0f, width = 13f, height = 11f) + val CLOSE_BUTTON_HOVERED = WidgetLocation.MISC.sprite(x = 51f, y = 11f, width = 13f, height = 11f) + val CLOSE_BUTTON_PRESSED = WidgetLocation.MISC.sprite(x = 51f, y = 22f, width = 13f, height = 11f) const val TAB_HEIGHT = 28f const val TAB_WIDTH = 28f 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 759e7bdba..31e47b284 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 @@ -3,26 +3,38 @@ package ru.dbotthepony.mc.otm.client.screen.panels.button import com.mojang.blaze3d.platform.InputConstants import net.minecraft.ChatFormatting import net.minecraft.client.gui.GuiGraphics +import net.minecraft.network.chat.Component 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.capability.addUpgradeTooltipLines import ru.dbotthepony.mc.otm.client.isCtrlDown import ru.dbotthepony.mc.otm.client.isShiftDown import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.render.AbstractMatterySprite 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.slot.AbstractSlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel +import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel import ru.dbotthepony.mc.otm.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.value +import ru.dbotthepony.mc.otm.menu.UpgradeSlots import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.FluidConfigPlayerInput import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput import java.util.function.Predicate +import kotlin.math.ceil +import kotlin.math.pow private fun > makeRedstoneSettingButton( screen: S, @@ -212,6 +224,7 @@ private fun > makeItemHandlerControlPanel( moveButtons(front, back, left, right, top, bottom) screen.addPanel(frame) frame.requestFocus() + frame.closeOnEscape = true return frame } @@ -240,6 +253,7 @@ private fun > makeEnergyConfigPanel( moveButtons(front, back, left, right, top, bottom) screen.addPanel(frame) frame.requestFocus() + frame.closeOnEscape = true return frame } @@ -268,6 +282,7 @@ private fun > makeFluidConfigPanel( moveButtons(front, back, left, right, top, bottom) screen.addPanel(frame) frame.requestFocus() + frame.closeOnEscape = true return frame } @@ -281,12 +296,17 @@ class DeviceControls>( val energyConfig: EnergyConfigPlayerInput? = null, val fluidConfig: FluidConfigPlayerInput? = null, val balanceInputs: BooleanInputWithFeedback? = null, + val upgrades: UpgradeSlots? = null, ) : EditablePanel(screen, parent, x = parent.width + 3f, height = 0f, width = 0f) { val itemConfigButton: LargeRectangleButtonPanel? val energyConfigButton: LargeRectangleButtonPanel? val fluidConfigButton: LargeRectangleButtonPanel? val redstoneControlsButton: LargeEnumRectangleButtonPanel? val balanceInputsButton: LargeBooleanRectangleButtonPanel? + val upgradesButton: LargeRectangleButtonPanel? + + private var upgradeWindow: FramePanel? = null + private var nextY = 0f fun

> addButton(button: P): P { @@ -310,6 +330,82 @@ class DeviceControls>( redstoneControlsButton = null } + if (upgrades != null) { + upgradesButton = addButton(object : LargeRectangleButtonPanel( + screen, this@DeviceControls, + skinElement = Widgets18.UPGRADES + ) { + init { + tooltip = TranslatableComponent("otm.gui.upgrades") + } + + override fun tickInner() { + super.tickInner() + + tooltipList = ArrayList().also { + it.add(TranslatableComponent("otm.gui.upgrades")) + it.add(TextComponent("")) + + if (upgrades.allowedTypes.isEmpty()) { + it.add(TranslatableComponent("otm.gui.upgrade_type.allowed_none").withStyle(ChatFormatting.DARK_GRAY)) + } else { + it.add(TranslatableComponent("otm.gui.upgrade_type.allowed").withStyle(ChatFormatting.GRAY)) + + for (upgrade in upgrades.allowedTypes) { + it.add(upgrade.component.copy().withStyle(ChatFormatting.GRAY)) + } + } + + val i = it.size + upgrades.currentStats.addUpgradeTooltipLines(it) + + if (it.size != i) { + it.add(i, TranslatableComponent("otm.gui.upgrades.current").withStyle(ChatFormatting.GRAY)) + it.add(i, TextComponent("")) + } + } + } + + override fun onClick(mouseButton: Int) { + if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) { + if (upgradeWindow != null && !upgradeWindow!!.isRemoved) { + upgradeWindow!!.toScreenCenter() + upgradeWindow!!.popup() + } else { + val square = ceil(upgrades.slots.size.toDouble().pow(0.5)).toInt() + val size = square * AbstractSlotPanel.SIZE + + upgradeWindow = FramePanel(screen, (size + 40f).coerceAtLeast(120f), 30f + size, TranslatableComponent("otm.gui.upgrades")).also { frame -> + val grid = GridPanel(screen, frame, width = AbstractSlotPanel.SIZE * square, height = AbstractSlotPanel.SIZE * square, columns = square, rows = square) + + for (slot in upgrades.slots) { + SlotPanel(screen, grid, slot) + } + + grid.dock = Dock.FILL + grid.dockResize = DockResizeMode.NONE + + screen.addPanel(frame) + + val parentFrame = this@DeviceControls.parent!! + + frame.behaveAsWindow() + frame.setPos(parentFrame.absoluteX + parentFrame.width / 2f - frame.width / 2f, parentFrame.absoluteY + parentFrame.height / 2f - frame.height / 2f) + frame.popup() + + upgrades.openState.value = true + frame.onClose { upgrades.openState.value = false } + + parentFrame.blockingWindow = frame + } + } + } + } + }) + } else { + upgradesButton = null + } + if (balanceInputs != null) { balanceInputsButton = addButton(LargeBooleanRectangleButtonPanel( screen, this, @@ -396,8 +492,9 @@ fun > makeDeviceControls( energyConfig: EnergyConfigPlayerInput? = null, fluidConfig: FluidConfigPlayerInput? = null, balanceInputs: BooleanInputWithFeedback? = null, + upgrades: UpgradeSlots? = null, ): DeviceControls { return DeviceControls(screen, parent, extra = extra, redstoneConfig = redstoneConfig, itemConfig = itemConfig, energyConfig = energyConfig, fluidConfig = fluidConfig, - balanceInputs = balanceInputs) + balanceInputs = balanceInputs, upgrades = upgrades) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/QueryUserPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/QueryUserPanel.kt index 4b7a8ec99..b36deebb1 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/QueryUserPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/input/QueryUserPanel.kt @@ -63,6 +63,7 @@ open class QueryUserPanel( this.width = maxWidth.coerceAtLeast(bottom.children.stream().mapToDouble { it.width.toDouble() }.sum().toFloat() + 2f) + PADDING * 2f toScreenCenter() + behaveAsWindow() } override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt index 2f3eda8cb..16d955ba2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/AndroidStationScreen.kt @@ -636,7 +636,7 @@ class AndroidStationScreen constructor(p_97741_: AndroidStationMenu, p_97742_: I researchCanvas.setDockMargin(4f, 4f, 4f, 4f) research!!.toScreenCenter() - research!!.closeOnEscape = true + research!!.behaveAsWindow() addPanel(research!!) research!!.requestFocus() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PlatePressScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PlatePressScreen.kt index d937ce9fd..3a3d28b6e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PlatePressScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/PlatePressScreen.kt @@ -8,7 +8,6 @@ import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel import ru.dbotthepony.mc.otm.client.screen.panels.button.makeDeviceControls import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel -import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu @@ -24,7 +23,7 @@ class PlatePressScreen(menu: PlatePressMenu, inventory: Inventory, title: Compon ProgressGaugePanel(this, frame, menu.progressGauge, 78f, PROGRESS_ARROW_TOP) SlotPanel(this, frame, menu.outputSlot, 104f, PROGRESS_SLOT_TOP) - makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig) + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig, upgrades = menu.upgrades) return frame } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/TwinPlatePressScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/TwinPlatePressScreen.kt index 6a2952f8c..946201340 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/TwinPlatePressScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/tech/TwinPlatePressScreen.kt @@ -27,7 +27,7 @@ class TwinPlatePressScreen(menu: TwinPlatePressMenu, inventory: Inventory, title ProgressGaugePanel(this, frame, menu.progressGauge1, 78f, PROGRESS_ARROW_TOP + 10f) SlotPanel(this, frame, menu.outputSlots[1], 104f, PROGRESS_SLOT_TOP + 10f) - makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig, balanceInputs = menu.balanceInputs) + makeDeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig, itemConfig = menu.itemConfig, balanceInputs = menu.balanceInputs, upgrades = menu.upgrades) return frame } 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 8fc6c4cef..607a4e858 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/config/MachinesConfig.kt @@ -10,6 +10,7 @@ import ru.dbotthepony.mc.otm.block.entity.tech.AndroidStationBlockEntity import ru.dbotthepony.mc.otm.block.entity.tech.ChemicalGeneratorBlockEntity import ru.dbotthepony.mc.otm.capability.energy.BlockEnergyStorageImpl import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.math.defineDecimal import ru.dbotthepony.mc.otm.registry.MNames object MachinesConfig : AbstractConfig("machines") { @@ -42,4 +43,34 @@ object MachinesConfig : AbstractConfig("machines") { } val ANDROID_CHARGER = BlockEnergyStorageImpl.makeConfigEntry(builder, MNames.ANDROID_CHARGER, capacity = Decimal(400_000), throughput = Decimal(8192)) { AndroidCharger } + + object Upgrades { + init { + builder.push("UPGRADES") + } + + val MIN_SPEED: Double by builder + .comment("Minimal combined upgrade speed percentage (upgrades can't decrease machine speed below this)") + .defineInRange("MIN_SPEED", -0.8, -1.0, Double.MAX_VALUE) + + val MAX_SPEED: Double by builder + .comment("Maximal combined upgrade speed percentage") + .defineInRange("MAX_SPEED", Double.MAX_VALUE, 0.0, Double.MAX_VALUE) + + val MIN_ENERGY by builder + .comment("Minimal combined energy consumption percentage (upgrades can't decrease machine energy consumption below this)") + .defineDecimal("MIN_ENERGY", Decimal(-0.8), Decimal(-1.0)) + + val MAX_ENERGY by builder + .comment("Maximal combined energy consumption percentage") + .defineDecimal("MAX_ENERGY", Decimal.LONG_MAX_VALUE, Decimal.ZERO) + + init { + builder.pop() + } + } + + init { + Upgrades + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt new file mode 100644 index 000000000..d93c2c282 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/UpgradeContainer.kt @@ -0,0 +1,54 @@ +package ru.dbotthepony.mc.otm.container + +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.config.ConciseBalanceValues +import ru.dbotthepony.mc.otm.config.VerboseBalanceValues +import ru.dbotthepony.mc.otm.core.isNotEmpty +import ru.dbotthepony.mc.otm.core.math.Decimal +import kotlin.math.pow + +open class UpgradeContainer(slotCount: Int, open val allowedUpgrades: Set = UpgradeType.ALL, watcher: Runnable = Runnable {}) : MatteryContainer(watcher, slotCount), IMatteryUpgrade { + constructor(watcher: Runnable, slotCount: Int, allowedUpgrades: Set) : this(slotCount, allowedUpgrades, watcher) + + final override val upgradeTypes: Set + get() = setOf() + + override val speedBonus: Double + get() = stream().filter { it.isNotEmpty }.mapToDouble { it.getCapability(MatteryCapability.UPGRADE).map { it.speedBonus }.orElse(0.0) * it.count }.sum() + override val processingItems: Int + get() = stream().filter { it.isNotEmpty }.mapToInt { it.getCapability(MatteryCapability.UPGRADE).map { it.processingItems }.orElse(0).coerceAtLeast(0) * it.count }.sum() + override val energyStorageFlat: Decimal + get() = stream().filter { it.isNotEmpty }.map { it.getCapability(MatteryCapability.UPGRADE).map { it.energyStorageFlat }.orElse(Decimal.ZERO).moreThanZero() * it.count }.reduce(Decimal.ZERO, Decimal::plus) + override val energyStorage: Decimal + get() = stream().filter { it.isNotEmpty }.map { it.getCapability(MatteryCapability.UPGRADE).map { it.energyStorage }.orElse(Decimal.ZERO).moreThanZero() * it.count }.reduce(Decimal.ZERO, Decimal::plus) + override val energyConsumed: Decimal + get() = stream().filter { it.isNotEmpty }.map { it.getCapability(MatteryCapability.UPGRADE).map { it.energyConsumed }.orElse(Decimal.ZERO) * it.count }.reduce(Decimal.ZERO, Decimal::plus) + override val failureMultiplier: Double + get() = stream().filter { it.isNotEmpty }.mapToDouble { it.getCapability(MatteryCapability.UPGRADE).map { it.failureMultiplier }.orElse(1.0).coerceAtLeast(0.0).pow(it.count.toDouble()) }.reduce(1.0) { a, b -> a * b } + override val energyThroughputFlat: Decimal + get() = stream().filter { it.isNotEmpty }.map { it.getCapability(MatteryCapability.UPGRADE).map { it.energyThroughputFlat }.orElse(Decimal.ZERO).moreThanZero() * it.count }.reduce(Decimal.ZERO, Decimal::plus) + override val energyThroughput: Decimal + get() = stream().filter { it.isNotEmpty }.map { it.getCapability(MatteryCapability.UPGRADE).map { it.energyThroughput }.orElse(Decimal.ZERO).moreThanZero() * it.count }.reduce(Decimal.ZERO, Decimal::plus) + + fun transform(values: ConciseBalanceValues): ConciseBalanceValues { + return object : ConciseBalanceValues { + override val capacity: Decimal + get() = values.capacity * (energyStorage + Decimal.ONE) + energyStorageFlat + override val throughput: Decimal + get() = values.throughput * (energyThroughput + Decimal.ONE) + energyThroughputFlat + } + } + + fun transform(values: VerboseBalanceValues): VerboseBalanceValues { + return object : VerboseBalanceValues { + override val capacity: Decimal + get() = values.capacity * (energyStorage + Decimal.ONE) + energyStorageFlat + override val receive: Decimal + get() = values.receive * (energyThroughput + Decimal.ONE) + energyThroughputFlat + override val extract: Decimal + get() = values.extract * (energyThroughput + Decimal.ONE) + energyThroughputFlat + } + } +} 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 efbf72a36..2a1d8b81d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -91,8 +91,8 @@ inline fun LazyOptional.ifPresentK(lambda: (T) -> Unit) { val ItemStack.tagNotNull: CompoundTag get() = orCreateTag -val FluidStack.isNotEmpty get() = !isEmpty -val ItemStack.isNotEmpty get() = !isEmpty +inline val FluidStack.isNotEmpty get() = !isEmpty +inline val ItemStack.isNotEmpty get() = !isEmpty inline var Entity.position: Vec3 get() = position() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalEnumSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalEnumSet.kt new file mode 100644 index 000000000..9dbbf12ab --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalEnumSet.kt @@ -0,0 +1,41 @@ +package ru.dbotthepony.mc.otm.core.collect + +import java.util.EnumMap +import java.util.function.BooleanSupplier +import java.util.stream.Stream + +class ConditionalEnumSet>private constructor(private val backing: EnumMap, marker: Unit) : Set { + constructor(clazz: Class) : this(EnumMap(clazz), Unit) + constructor(map: Map) : this(EnumMap(map), Unit) + + override val size: Int + get() = backing.values.stream().filter { it.asBoolean }.count().toInt() + + override fun contains(element: T): Boolean { + return backing[element]?.asBoolean ?: false + } + + override fun containsAll(elements: Collection): Boolean { + return elements.all { contains(it) } + } + + override fun isEmpty(): Boolean { + return backing.isEmpty() || backing.values.none { it.asBoolean } + } + + override fun stream(): Stream { + return backing.entries.stream().filter { it.value.asBoolean }.map { it.key } + } + + override fun iterator(): Iterator { + return backing.entries.iterator().filter { it.value.asBoolean }.map { it.key } + } + + fun add(value: T, condition: BooleanSupplier) { + backing[value] = condition + } + + fun remove(value: T): Boolean { + return backing.remove(value) != null + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt index 781f9ef47..9596f15e9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ConditionalSet.kt @@ -1,68 +1,57 @@ package ru.dbotthepony.mc.otm.core.collect +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap +import java.util.function.BooleanSupplier import java.util.stream.Stream -class ConditionalSet : AbstractSet { - // method without boxing - fun interface Condition { - fun check(): Boolean +class ConditionalSet : Set { + private val backing = Object2ObjectLinkedOpenHashMap() + + override val size: Int + get() = backing.values.stream().filter { it.asBoolean }.count().toInt() + + override fun contains(element: E): Boolean { + return backing[element]?.asBoolean ?: false } - private val getters: Array> - - constructor(vararg getters: Pair) : super() { - this.getters = Array(getters.size) { getters[it] } + override fun containsAll(elements: Collection): Boolean { + return elements.all { contains(it) } } - constructor(getters: List>) : super() { - this.getters = Array(getters.size) { getters[it] } + override fun isEmpty(): Boolean { + return backing.isEmpty() || backing.values.stream().noneMatch { it.asBoolean } } - constructor(getters: Stream>) : super() { - this.getters = getters.toArray { arrayOfNulls>(it) } + override fun stream(): Stream { + return backing.entries.stream().filter { it.value.asBoolean }.map { it.key } } - override val size: Int get() { - var i = 0 - - for (pair in getters) { - if (pair.first.check()) { - i++ - } - } - - return i + override fun iterator(): Iterator { + return backing.entries.iterator().filter { it.value.asBoolean }.map { it.key } } - override fun iterator(): Iterator { - return object : Iterator { - val parent = getters.iterator() - private var pair: Pair? = null + fun add(element: E, condition: BooleanSupplier): Boolean { + if (element in backing) return false + backing[element] = condition + return true + } - private fun search() { - for (pair in parent) { - if (pair.first.check()) { - this.pair = pair - return - } - } + fun addFirst(element: E, condition: BooleanSupplier): Boolean { + if (element in backing) return false + backing.putAndMoveToFirst(element, condition) + return true + } - this.pair = null - } + fun replace(element: E, condition: BooleanSupplier) { + backing[element] = condition + } - init { - search() - } + fun replaceFirst(element: E, condition: BooleanSupplier) { + backing.remove(element) + backing.putAndMoveToFirst(element, condition) + } - override fun hasNext(): Boolean { - return pair != null - } - - override fun next(): T { - val pair = pair ?: throw NoSuchElementException() - search() - return pair.second - } - } + fun remove(element: E): Boolean { + return backing.remove(element) != null } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt new file mode 100644 index 000000000..66fdf57bd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/SimpleUpgrade.kt @@ -0,0 +1,51 @@ +package ru.dbotthepony.mc.otm.item + +import net.minecraft.core.Direction +import net.minecraft.nbt.CompoundTag +import net.minecraft.network.chat.Component +import net.minecraft.world.item.Item +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.TooltipFlag +import net.minecraft.world.level.Level +import net.minecraftforge.common.capabilities.Capability +import net.minecraftforge.common.capabilities.ICapabilityProvider +import net.minecraftforge.common.util.LazyOptional +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.UpgradeType +import ru.dbotthepony.mc.otm.capability.addUpgradeTooltipLines +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.math.Decimal + +class SimpleUpgrade( + properties: Properties, + override val upgradeTypes: Set, + override val speedBonus: Double = 0.0, + override val processingItems: Int = 0, + override val energyStorageFlat: Decimal = Decimal.ZERO, + override val energyStorage: Decimal = Decimal.ZERO, + override val energyConsumed: Decimal = Decimal.ZERO, + override val energyThroughputFlat: Decimal = Decimal.ZERO, + override val energyThroughput: Decimal = Decimal.ZERO, + override val failureMultiplier: Double = 1.0, +) : Item(properties), IMatteryUpgrade, ICapabilityProvider { + private val resolver = LazyOptional.of { this } + + override fun getCapability(cap: Capability, side: Direction?): LazyOptional { + if (cap === MatteryCapability.UPGRADE) { + return resolver.cast() + } + + return LazyOptional.empty() + } + + override fun appendHoverText(p_41421_: ItemStack, p_41422_: Level?, p_41423_: MutableList, p_41424_: TooltipFlag) { + super.appendHoverText(p_41421_, p_41422_, p_41423_, p_41424_) + addUpgradeTooltipLines(p_41423_) + } + + override fun initCapabilities(stack: ItemStack, nbt: CompoundTag?): ICapabilityProvider { + return this + } +} + 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 bc92c1a77..24429784e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/MatteryMenu.kt @@ -11,6 +11,7 @@ import net.minecraft.network.FriendlyByteBuf import net.minecraft.resources.ResourceLocation import net.minecraft.server.level.ServerPlayer import net.minecraft.world.Container +import net.minecraft.world.SimpleContainer import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu @@ -23,6 +24,9 @@ import net.minecraft.world.item.enchantment.EnchantmentHelper.hasBindingCurse import net.minecraft.world.level.block.entity.BlockEntity import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.PacketDistributor +import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade +import ru.dbotthepony.mc.otm.capability.MatteryCapability +import ru.dbotthepony.mc.otm.capability.UpgradeType import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorSlots @@ -31,7 +35,12 @@ import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.ItemFilterNetworkSlot import ru.dbotthepony.mc.otm.container.MatteryContainer +import ru.dbotthepony.mc.otm.container.UpgradeContainer import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.collect.ConditionalEnumSet +import ru.dbotthepony.mc.otm.core.collect.ConditionalSet +import ru.dbotthepony.mc.otm.core.immutableList +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.util.BigDecimalValueCodec import ru.dbotthepony.mc.otm.core.util.BinaryStringCodec import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec @@ -49,10 +58,14 @@ import ru.dbotthepony.mc.otm.network.packetHandled import ru.dbotthepony.mc.otm.network.sender import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer import ru.dbotthepony.mc.otm.network.synchronizer.IField +import ru.dbotthepony.mc.otm.network.synchronizer.IMutableBooleanField import java.io.DataInputStream import java.io.DataOutputStream import java.math.BigDecimal import java.util.* +import java.util.function.BooleanSupplier +import java.util.function.DoubleSupplier +import java.util.function.IntSupplier import java.util.function.Predicate import java.util.function.Supplier @@ -63,6 +76,17 @@ data class EquipmentSlots( val curiosSlots: List> ) +/** + * [openState] **is clientside only**, attempting to use it on server will result + * in classloading exceptions. + */ +data class UpgradeSlots( + val slots: List, + val allowedTypes: Set, + val openState: GetterSetter, + val currentStats: IMatteryUpgrade +) + abstract class MatteryMenu @JvmOverloads protected constructor( menuType: MenuType<*>?, containerId: Int, @@ -382,7 +406,7 @@ 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 } - private val externalSlots = ArrayList() + private val externalSlots = ConditionalSet() private val quickMoveMapping = Reference2ObjectOpenHashMap>>() override fun addSlot(pSlot: Slot): Slot { @@ -412,11 +436,17 @@ abstract class MatteryMenu @JvmOverloads protected constructor( /** * Adds slot to "storage slots" list (utilized by quick move) and calls [addSlot] + * + * [condition] allows to specify when slot is invisible on GUI (hence being ignored by quick move) */ - protected fun addStorageSlot(slot: T, addMapping: Boolean = true): T { + protected fun addStorageSlot(slot: T, addMapping: Boolean = true, prepend: Boolean = false, condition: BooleanSupplier = BooleanSupplier { true }): T { if (slot !in externalSlots) { addSlot(slot) - externalSlots.add(slot) + + if (prepend) + externalSlots.replaceFirst(slot, condition) + else + externalSlots.replace(slot, condition) if (addMapping) { mapQuickMove(slot, equipmentSlots) @@ -446,8 +476,8 @@ abstract class MatteryMenu @JvmOverloads protected constructor( mapQuickMove(slot, externalSlots, prepend = prepend) } - protected fun mapQuickMove(slot: Slot, target: Collection, prepend: Boolean = false) { - val listing = quickMoveMapping.computeIfAbsent(slot, Reference2ObjectFunction { ReferenceArrayList(1) }) + protected fun mapQuickMove(slot: Slot, target: Collection, prepend: Boolean = false, condition: BooleanSupplier = BooleanSupplier { true }) { + val listing = quickMoveMapping.computeIfAbsent(slot, Reference2ObjectFunction { ReferenceArrayList(1) /* ReferenceArrayList ибо мы используем его в некотором смысле как множество */ }) listing.remove(target) if (prepend) @@ -715,6 +745,56 @@ abstract class MatteryMenu @JvmOverloads protected constructor( ) } + fun makeUpgradeSlots(count: Int, container: UpgradeContainer?): UpgradeSlots { + if (container != null) { + require(count == container.containerSize) { "Upgrade container size ${container.containerSize} does not match with provided size $count" } + } + + val allowedTypes = EnumMap(UpgradeType::class.java) + + for (value in UpgradeType.ALL) { + allowedTypes[value] = mSynchronizer.bool() + + if (container != null) { + allowedTypes[value]!!.boolean = value in container.allowedUpgrades + } + } + + val syncContainer = container ?: SimpleContainer(count) + + var isOpen = false + val input = PlayerInput(BooleanValueCodec, allowSpectators = true, handler = { isOpen = it }) + + return UpgradeSlots( + slots = immutableList(count) { + object : MatterySlot(syncContainer, it) { + init { + mapQuickMoveToInventory(this) + addStorageSlot(this, prepend = true, condition = { isOpen }) + } + + override fun mayPlace(itemStack: ItemStack): Boolean { + return super.mayPlace(itemStack) && itemStack.getCapability(MatteryCapability.UPGRADE).map { it.upgradeTypes.any { allowedTypes[it]!!.boolean } }.orElse(false) + } + } + }, + + allowedTypes = ConditionalEnumSet(allowedTypes), + openState = GetterSetter.of({ isOpen }, { input.input(it); isOpen = it }), + currentStats = object : IMatteryUpgrade { + override val upgradeTypes: Set = setOf() + override val speedBonus: Double by mSynchronizer.computedDouble(DoubleSupplier { container?.speedBonus ?: 0.0 }).property + override val processingItems: Int by mSynchronizer.computedInt(IntSupplier { container?.processingItems ?: 0 }).property + override val energyStorageFlat: Decimal by mSynchronizer.computedDecimal { container?.energyStorageFlat ?: Decimal.ZERO } + override val energyStorage: Decimal by mSynchronizer.computedDecimal { container?.energyStorage ?: Decimal.ZERO } + override val energyConsumed: Decimal by mSynchronizer.computedDecimal { container?.energyConsumed ?: Decimal.ZERO } + override val energyThroughputFlat: Decimal by mSynchronizer.computedDecimal { container?.energyThroughputFlat ?: Decimal.ZERO } + override val energyThroughput: Decimal by mSynchronizer.computedDecimal { container?.energyThroughput ?: Decimal.ZERO } + override val failureMultiplier: Double by mSynchronizer.computedDouble(DoubleSupplier { container?.failureMultiplier ?: 1.0 }).property + } + ) + } + companion object { val TEXTURE_EMPTY_SLOTS: List = ImmutableList.of( InventoryMenu.EMPTY_ARMOR_SLOT_BOOTS, 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 68ee7387d..01f2cc395 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt @@ -10,6 +10,7 @@ import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.energy import ru.dbotthepony.mc.otm.client.minecraft +import ru.dbotthepony.mc.otm.container.UpgradeContainer import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.runOnClient diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt index 0f33d9ed6..935922882 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/PlatePressMenu.kt @@ -26,6 +26,8 @@ class PlatePressMenu @JvmOverloads constructor( val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig, allowPull = true) val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget) + val upgrades = makeUpgradeSlots(3, tile?.upgrades) + init { addStorageSlot(inputSlot) addStorageSlot(outputSlot) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/TwinPlatePressMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/TwinPlatePressMenu.kt index f6bda5c84..e5168c070 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/TwinPlatePressMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/tech/TwinPlatePressMenu.kt @@ -31,6 +31,8 @@ class TwinPlatePressMenu @JvmOverloads constructor( val balanceInputs = BooleanInputWithFeedback(this) + val upgrades = makeUpgradeSlots(4, tile?.upgrades) + init { if (tile != null) { balanceInputs.with(tile::balanceInputs) 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 6434da4ae..44a35bf41 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MCreativeTabs.kt @@ -130,6 +130,7 @@ private fun CreativeModeTab.Output.fluids(value: Item) { private fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) { with(consumer) { accept(MItems.MACHINES) + accept(MItems.CreativeUpgrades.LIST) accept(MRegistry.CARGO_CRATES.item) accept(MItems.HOLO_SIGN) 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 4577d88c7..ea6697944 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt @@ -4,7 +4,6 @@ package ru.dbotthepony.mc.otm.registry import net.minecraft.ChatFormatting import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation -import net.minecraft.world.entity.EquipmentSlot import net.minecraft.world.food.FoodProperties import net.minecraft.world.item.* import net.minecraft.world.item.crafting.Ingredient @@ -15,9 +14,11 @@ import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.registries.DeferredRegister import net.minecraftforge.registries.ForgeRegistries import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.capability.UpgradeType import ru.dbotthepony.mc.otm.config.ItemsConfig import ru.dbotthepony.mc.otm.core.collect.SupplierList import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.item.* import ru.dbotthepony.mc.otm.item.exopack.ExoPackCraftingUpgradeItem import ru.dbotthepony.mc.otm.item.exopack.ExoPackProbeItem @@ -160,6 +161,38 @@ object MItems { TRITANIUM_ANVIL = SupplierList(props) } + object CreativeUpgrades { + val SPEED: SimpleUpgrade by registry.register("creative_speed_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.SPEED.set(), speedBonus = 1.0) } + val ENERGY_CONSUMPTION: SimpleUpgrade by registry.register("creative_energy_consumption_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_CONSUMPTION.set(), energyConsumed = Decimal.MINUS_ONE) } + val ENERGY_STORAGE: SimpleUpgrade by registry.register("creative_energy_storage_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_STORAGE.set(), energyStorage = Decimal.ONE) } + val ENERGY_STORAGE_FLAT: SimpleUpgrade by registry.register("creative_energy_storage_flat_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_STORAGE.set(), energyStorageFlat = Decimal.LONG_MAX_VALUE) } + val ENERGY_STORAGE_FLAT_SMALL: SimpleUpgrade by registry.register("creative_energy_storage_flat2_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_STORAGE.set(), energyStorageFlat = Decimal.INT_MAX_VALUE) } + val ENERGY_THROUGHPUT: SimpleUpgrade by registry.register("creative_energy_throughput_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_THROUGHPUT.set(), energyThroughput = Decimal.ONE) } + val ENERGY_THROUGHPUT_FLAT: SimpleUpgrade by registry.register("creative_energy_throughput_flat_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_THROUGHPUT.set(), energyThroughputFlat = Decimal.LONG_MAX_VALUE) } + val ENERGY_THROUGHPUT_FLAT_SMALL: SimpleUpgrade by registry.register("creative_energy_throughput_flat2_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.ENERGY_THROUGHPUT.set(), energyThroughputFlat = Decimal.INT_MAX_VALUE) } + val FAILSAFE: SimpleUpgrade by registry.register("creative_failsafe_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.FAILSAFE.set(), failureMultiplier = 0.0) } + val FAILURE: SimpleUpgrade by registry.register("creative_failure_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.FAILSAFE.set(), failureMultiplier = 2.0) } + val PROCESSING_ITEMS: SimpleUpgrade by registry.register("creative_processing_upgrade") { SimpleUpgrade(Item.Properties().rarity(Rarity.EPIC), UpgradeType.PROCESSING.set(), processingItems = 1) } + + val LIST = SupplierList( + ::SPEED, + ::ENERGY_CONSUMPTION, + ::ENERGY_STORAGE, + ::ENERGY_STORAGE_FLAT, + ::ENERGY_STORAGE_FLAT_SMALL, + ::ENERGY_THROUGHPUT, + ::ENERGY_THROUGHPUT_FLAT, + ::ENERGY_THROUGHPUT_FLAT_SMALL, + ::FAILSAFE, + ::FAILURE, + ::PROCESSING_ITEMS, + ) + } + + init { + CreativeUpgrades + } + val MATTER_DUST: Item by registry.register(MNames.MATTER_DUST) { MatterDustItem() } val TRITANIUM_ORE_CLUMP: Item by registry.register(MNames.TRITANIUM_ORE_CLUMP) { Item(DEFAULT_PROPERTIES) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Tags.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Tags.kt index a7d5a8e79..ae77498d5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Tags.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/Tags.kt @@ -25,6 +25,7 @@ object MItemTags { val CARGO_CRATES: TagKey = ItemTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "cargo_crates")) val MINECART_CARGO_CRATES: TagKey = ItemTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "minecart_cargo_crates")) val INDUSTRIAL_GLASS: TagKey = ItemTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "industrial_glass")) + val UPGRADES: TagKey = ItemTags.create(ResourceLocation(OverdriveThatMatters.MOD_ID, "upgrades")) val CRAFTING_TABLES: TagKey = ItemTags.create(ResourceLocation("forge", "crafting_tables")) diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/misc.png index 8923a02d7129ba396ac5c898077776eab444eda6..bc3b614b3dd43b4cce917a916ff7fc140deec3db 100644 GIT binary patch delta 1299 zcmV+u1?>9N1Fs5@8Gi-<0063Kaozv`0fcEoLr_UWLm+T+Z)Rz1WdHyuk$sUpNW(xJ z#b47(RVpHO5OK&*o$R6_j-`r4uu$3xtvZ-o`UOoIk`xz5!L{Jv$70pN#aUMeS3wZ` z0C9D3Qgo3L|Cbb6#CUMrk9YSTckck9S!Jpjhyki*8Ry{D6reiwfL2XskIMF-~x1PwGB!$4z|0009l zNklbu^{sX!c8@!ZwNdEy@ z5CZAwp>eScB9J9y$ef{I67Z7sI>hJ7Mmjy|Wcdg8S;U#M?!NEdyZ3a5<#L&UB)YDH zZQFl1Yl_~-`+#8>3;@7&-M2rnx4$7GN=${SWj;oc3y{IGEU;~RrXzX|h{%G2Q~7at zNb2|H=7y#|FpmOchfq8KfaiHhRwPVPOzwgkG-) zrfDubH_8B`(MU=>#S`MhXT1sL!m`-7FyKarA~z4V4-p^d30WEd5s`VG zCnYv3mc>SeLDnIFl}bg}VOII!ri9cgLI8t#UQ~u=O`P!~OW~|zT+=kLEGx2NL_~kA zR;!sC;LqtPso#h5^LZ(psRwA9Hvcw2M6A_np`H-+dv5!~$qAU3mka=KWm$kR#zYli ztJTWf2=5;sN&S93Kf~991NeS-$5vVgtTP)NO(w$j))qoku1lr4=%dL5u1lrxETHSU zXv5kGBSeV@04UnF%?~qV-92f_!aRS%AkGLy0DyYEE;Ufv&)Pi1$9Y2R9^g0*IF7TG z?k{SS3=kB)jYg0zg&U2A)R$4-Y4F`(khl~^kUXU1wA<~4Y$AEQor>Sb$0RKuude1s zK$i4WQn2E@2&7tnT?QCDJ;ArzTbP%^olZw;;K0hq+B5Cl0EPRP7gE2!udjdbX@4Jn zK0L&i!j@%G3ndo3-3enn9`kx(<$DA5{-8iyS#q@~iv2u_9}Yh-<>I5x4ux4jHZ;}^ z<^G_UWOcU<%KgF4ehr4*A4GZpKgmgp@B16+{-QS71AO1#NP)SO!a?D_?~^>m^E?){ z+poGbJ{t}bXBlLSu`EJhmi}8<+_}J}-3#ngDV!<NS%G}H0G|+7 zb93|V?rtD2G&J=8|NqC19rN?^3kwS~H8lkadfCga1X5fjL4LtNSqNa*bVlbqP?WR4 zBeIx*f$uN~Gak=hkpi?()3e^iF(ktM?bMso4l8gtterIT!+(1l1rH-mTdml6d-PQm zW3FfI`si?mNom?7V+L0Lg-zTIAwi1H9v5CEo-TAe8(6BDv&!H91M8$@#_Ri)ZfJda zaar($cIB6!OBT4W&M;rKmW9=0wfa|$^RYQn zDz~uin($H1Vd58amfU@OYOK4G)Xz29Y5tGv;9@Z0;Hvl_v^Sl@LUD)V+wHX`4v+Tz zKmO0V#<0Y5!SCZ1?^yRq?3%%#vq3p`G4n!i;euwV)Y*-a2M=y}aO^tk@75IzIvt!l z-bw6k}sn;6@9+$#Mq5qL1KF^w&g>D_L(S&~n9op&sp s={V_MTV#`J!n6d=8LAbL4L{f?EGRR&WxDMWFfbWBUHx3vIVCg!07Q7ai~s-t diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.png index 2c2c135b45749997a8b2951fe4eaa463661da934..54effb4a70e92979c02510d7900df312682508c1 100644 GIT binary patch delta 2899 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 delta 2720 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 diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/widgets/side_controls.xcf index 3182f633b7e8dd318147176c666ef86ab1156cab..cbb5a84e4b0545bf1529bc4c8b29b40395524bb1 100644 GIT binary patch delta 8031 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 YVRf^+DY@){N-lehG&MW0E{mP|FZ=9X_W%F@ delta 8441 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 Sk)<@;L1gK#>ycRP#D4>tK;n@A