Bring storage system back to functional state

Add ISubscriptable, which is implemented by networked fields and networked inputs
Unify storage grid code
Add itemstack and itemstoragestack sorters
This commit is contained in:
DBotThePony 2023-08-10 12:55:45 +07:00
parent 0754cf55eb
commit 506017f055
Signed by: DBot
GPG Key ID: DCC23B5715498507
62 changed files with 1783 additions and 1337 deletions

View File

@ -213,6 +213,27 @@ private fun misc(provider: MatteryLanguageProvider) {
misc("suffix.zepto", "%s z%s") misc("suffix.zepto", "%s z%s")
misc("suffix.yocto", "%s y%s") misc("suffix.yocto", "%s y%s")
misc("suffix_concise.none", "%s")
misc("suffix_concise.kilo", "%sk")
misc("suffix_concise.mega", "%sM")
misc("suffix_concise.giga", "%sG")
misc("suffix_concise.tera", "%sT")
misc("suffix_concise.peta", "%sP")
misc("suffix_concise.exa", "%sE")
misc("suffix_concise.zetta", "%sZ")
misc("suffix_concise.yotta", "%sY")
misc("suffix_concise.deci", "%sd")
misc("suffix_concise.centi", "%sc")
misc("suffix_concise.milli", "%sm")
misc("suffix_concise.micro", "%sμ")
misc("suffix_concise.nano", "%sn")
misc("suffix_concise.pico", "%sp")
misc("suffix_concise.femto", "%sf")
misc("suffix_concise.atto", "%sa")
misc("suffix_concise.zepto", "%sz")
misc("suffix_concise.yocto", "%sy")
misc("suffix_raw.kilo", "k") misc("suffix_raw.kilo", "k")
misc("suffix_raw.mega", "M") misc("suffix_raw.mega", "M")
misc("suffix_raw.giga", "G") misc("suffix_raw.giga", "G")
@ -776,7 +797,7 @@ private fun gui(provider: MatteryLanguageProvider) {
gui("sorting.name", "Sort by name") gui("sorting.name", "Sort by name")
gui("sorting.id", "Sort by ID") gui("sorting.id", "Sort by ID")
gui("sorting.modid", "Sort by Namespace (mod) ID") gui("sorting.modid", "Sort by Namespace (mod) ID")
gui("sorting.count", "Sort by amount") gui("sorting.count", "Sort by quantity")
gui("sorting.ascending", "Ascending") gui("sorting.ascending", "Ascending")
gui("sorting.descending", "Descending") gui("sorting.descending", "Descending")
gui("sorting.matter_value", "Matter value") gui("sorting.matter_value", "Matter value")

View File

@ -221,6 +221,26 @@ private fun misc(provider: MatteryLanguageProvider) {
misc("suffix.zepto", "%s з%s") misc("suffix.zepto", "%s з%s")
misc("suffix.yocto", "%s и%s") misc("suffix.yocto", "%s и%s")
misc("suffix_concise.none", "%s")
misc("suffix_concise.kilo", "%sк")
misc("suffix_concise.mega", "%sМ")
misc("suffix_concise.giga", "%sГ")
misc("suffix_concise.tera", "%sТ")
misc("suffix_concise.peta", "%sП")
misc("suffix_concise.exa", "%sЭ")
misc("suffix_concise.zetta", "%sЗ")
misc("suffix_concise.yotta", "%sИ")
misc("suffix_concise.deci", "%sд")
misc("suffix_concise.centi", "%sс")
misc("suffix_concise.milli", "%sм")
misc("suffix_concise.micro", "%s к")
misc("suffix_concise.nano", "%sн")
misc("suffix_concise.pico", "%sп")
misc("suffix_concise.femto", "%sф")
misc("suffix_concise.atto", "%sа")
misc("suffix_concise.zepto", "%sз")
misc("suffix_concise.yocto", "%sи")
misc("suffix_raw.kilo", "к") misc("suffix_raw.kilo", "к")
misc("suffix_raw.mega", "М") misc("suffix_raw.mega", "М")
misc("suffix_raw.giga", "Г") misc("suffix_raw.giga", "Г")

View File

@ -142,7 +142,6 @@ public final class OverdriveThatMatters {
} }
private void setup(final FMLCommonSetupEvent event) { private void setup(final FMLCommonSetupEvent event) {
EVENT_BUS.addListener(EventPriority.LOWEST, DrivePool.INSTANCE::onServerPostTick);
EVENT_BUS.addListener(EventPriority.HIGHEST, DrivePool.INSTANCE::serverStopEvent); EVENT_BUS.addListener(EventPriority.HIGHEST, DrivePool.INSTANCE::serverStopEvent);
EVENT_BUS.addListener(EventPriority.LOWEST, DrivePool.INSTANCE::serverStartEvent); EVENT_BUS.addListener(EventPriority.LOWEST, DrivePool.INSTANCE::serverStartEvent);
EVENT_BUS.addListener(EventPriority.NORMAL, DrivePool.INSTANCE::onWorldSave); EVENT_BUS.addListener(EventPriority.NORMAL, DrivePool.INSTANCE::onWorldSave);

View File

@ -37,7 +37,6 @@ import net.minecraftforge.event.entity.player.PlayerEvent
import net.minecraftforge.event.level.ChunkWatchEvent import net.minecraftforge.event.level.ChunkWatchEvent
import net.minecraftforge.event.level.LevelEvent import net.minecraftforge.event.level.LevelEvent
import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.event.server.ServerStoppingEvent
import net.minecraftforge.items.IItemHandler
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
@ -45,7 +44,6 @@ import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.isMekanismLoaded import ru.dbotthepony.mc.otm.capability.isMekanismLoaded
import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper
import ru.dbotthepony.mc.otm.compat.mekanism.Mekanism2MatteryEnergyWrapper import ru.dbotthepony.mc.otm.compat.mekanism.Mekanism2MatteryEnergyWrapper
import ru.dbotthepony.mc.otm.core.collect.SupplierList
import ru.dbotthepony.mc.otm.core.collect.WeakHashSet import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
import ru.dbotthepony.mc.otm.core.forValidRefs import ru.dbotthepony.mc.otm.core.forValidRefs
import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.core.get

View File

@ -1,7 +1,6 @@
package ru.dbotthepony.mc.otm.block.entity package ru.dbotthepony.mc.otm.block.entity
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import mekanism.common.tile.interfaces.IRedstoneControl
import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState

View File

@ -20,7 +20,6 @@ import ru.dbotthepony.mc.otm.config.ItemsConfig
import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.orNull import ru.dbotthepony.mc.otm.core.orNull
import ru.dbotthepony.mc.otm.core.util.FluidStackValueCodec import ru.dbotthepony.mc.otm.core.util.FluidStackValueCodec

View File

@ -9,17 +9,14 @@ import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.items.IItemHandler
import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock
import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.block.entity.WorkerState
import ru.dbotthepony.mc.otm.capability.CombinedItemHandler
import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.ProxiedItemHandler import ru.dbotthepony.mc.otm.capability.ProxiedItemHandler
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage
import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl import ru.dbotthepony.mc.otm.capability.matter.MatterStorageImpl
import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage import ru.dbotthepony.mc.otm.capability.matter.ProfiledMatterStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.config.MachinesConfig

View File

@ -17,11 +17,12 @@ import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.menu.storage.DriveRackMenu import ru.dbotthepony.mc.otm.menu.storage.DriveRackMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.storage.* import ru.dbotthepony.mc.otm.storage.*
import ru.dbotthepony.mc.otm.storage.powered.PoweredComponent
import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent
class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : class DriveRackBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.DRIVE_RACK, blockPos, blockState) {
MatteryPoweredBlockEntity(MBlockEntities.DRIVE_RACK, p_155229_, p_155230_) {
val energy = WorkerEnergyStorage(this::setChangedLight, MachinesConfig.DRIVE_RACK) val energy = WorkerEnergyStorage(this::setChangedLight, MachinesConfig.DRIVE_RACK)
val cell = StorageNode(energy)
val container: MatteryContainer = object : MatteryContainer(this::setChanged, 4) { val container: MatteryContainer = object : MatteryContainer(this::setChanged, 4) {
override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
@ -31,21 +32,15 @@ class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
// but since we don't know generics of upvalue mattery drive, its storage type // but since we don't know generics of upvalue mattery drive, its storage type
// is defined as out variant // is defined as out variant
old.getCapability(MatteryCapability.DRIVE).ifPresent { old.getCapability(MatteryCapability.DRIVE).ifPresent {
cell.computeIfAbsent(it.storageType as StorageStack.Type<ItemStorageStack>) { cell.removeStorageComponent(PoweredComponent(it, ::energy))
PoweredVirtualComponent(VirtualComponent(it), ::energy)
}.remove(it as IMatteryDrive<ItemStorageStack>)
} }
new.getCapability(MatteryCapability.DRIVE).ifPresent { new.getCapability(MatteryCapability.DRIVE).ifPresent {
cell.computeIfAbsent(it.storageType as StorageStack.Type<ItemStorageStack>) { cell.addStorageComponent(PoweredComponent(it, ::energy))
PoweredVirtualComponent(VirtualComponent(it), ::energy)
}.add(it as IMatteryDrive<ItemStorageStack>)
} }
} }
}.also(::addDroppableContainer) }.also(::addDroppableContainer)
val cell = StorageNode(energy)
init { init {
savetable(::energy, ENERGY_KEY) savetable(::energy, ENERGY_KEY)
savetable(::container, INVENTORY_KEY) savetable(::container, INVENTORY_KEY)

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.mc.otm.block.entity.storage package ru.dbotthepony.mc.otm.block.entity.storage
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
@ -8,71 +10,88 @@ import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
import ru.dbotthepony.mc.otm.block.storage.DriveViewerBlock
import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.block.entity.WorkerState
import ru.dbotthepony.mc.otm.block.storage.DriveViewerBlock
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.core.get
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu
import ru.dbotthepony.mc.otm.storage.StorageStack import java.util.UUID
class DriveViewerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.DRIVE_VIEWER, p_155229_, p_155230_) { class DriveViewerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.DRIVE_VIEWER, blockPos, blockState) {
override fun setChanged() { val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyUpdated, MachinesConfig.DRIVE_VIEWER))
super.setChanged() val energyConfig = ConfigurableEnergy(energy)
val level = level val container: MatteryContainer = object : MatteryContainer(this::setChangedLight, 1) {
if (level is ServerLevel)
tickList.once {
var state = blockState
if (container.getItem(0).getCapability(MatteryCapability.DRIVE).isPresent) {
state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING)
} else {
state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE)
}
if (state !== blockState) {
level.setBlock(blockPos, state, Block.UPDATE_CLIENTS)
}
}
}
val energy = WorkerEnergyStorage(this::setChangedLight, MachinesConfig.DRIVE_VIEWER)
val container: MatteryContainer = object : MatteryContainer(this::setChanged, 1) {
override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
super.setChanged(slot, new, old) super.setChanged(slot, new, old)
val level = level val level = level
if (level is ServerLevel) if (level is ServerLevel) {
tickList.once { tickList.once {
if (!isRemoved) { val isPresent = new.getCapability(MatteryCapability.DRIVE).isPresent
var state = blockState var state = this@DriveViewerBlockEntity.blockState.setValue(DriveViewerBlock.DRIVE_PRESENT, isPresent)
if (new.getCapability(MatteryCapability.DRIVE).isPresent) { if (!isPresent) {
state = state.setValue(DriveViewerBlock.DRIVE_PRESENT, true) state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.IDLE)
} else { } else if (energy.batteryLevel >= Decimal.TEN && !redstoneControl.isBlockedByRedstone) {
state = state.setValue(DriveViewerBlock.DRIVE_PRESENT, false) state = state.setValue(WorkerState.SEMI_WORKER_STATE, WorkerState.WORKING)
} }
if (state !== blockState) { if (state !== this@DriveViewerBlockEntity.blockState) {
level.setBlock(blockPos, state, Block.UPDATE_CLIENTS) level.setBlock(this@DriveViewerBlockEntity.blockPos, state, Block.UPDATE_CLIENTS)
}
} }
} }
}
} }
}.also(::addDroppableContainer) }.also(::addDroppableContainer)
interface ISettings {
var sorting: ItemStorageStackSorter
var isAscending: Boolean
}
inner class Settings : ISettings {
override var sorting: ItemStorageStackSorter = ItemStorageStackSorter.DEFAULT
set(value) {
field = value
setChangedLight()
}
override var isAscending: Boolean = true
set(value) {
field = value
setChangedLight()
}
}
val settings = Object2ObjectOpenHashMap<UUID, Settings>()
fun getSettingsFor(player: Player): ISettings {
return settings.computeIfAbsent(player.uuid, Object2ObjectFunction { Settings() })
}
private fun energyUpdated() {
val state = blockState.setValue(WorkerState.SEMI_WORKER_STATE, if (energy.batteryLevel >= Decimal.TEN && !redstoneControl.isBlockedByRedstone && blockState[DriveViewerBlock.DRIVE_PRESENT]) WorkerState.WORKING else WorkerState.IDLE)
if (state != blockState) {
level?.setBlock(blockPos, state, Block.UPDATE_CLIENTS)
} else {
setChangedLight()
}
}
init { init {
savetable(::energy, ENERGY_KEY) savetable(::energy, ENERGY_KEY)
savetable(::container, INVENTORY_KEY) savetable(::container, INVENTORY_KEY)
exposeEnergyGlobally(energy)
} }
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {

View File

@ -1,10 +1,10 @@
package ru.dbotthepony.mc.otm.block.entity.storage package ru.dbotthepony.mc.otm.block.entity.storage
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.NonNullList import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container import net.minecraft.world.Container
@ -19,19 +19,18 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.common.ForgeHooks import net.minecraftforge.common.ForgeHooks
import net.minecraftforge.common.util.INBTSerializable import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.network.NetworkEvent
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.graph.storage.StorageNode import ru.dbotthepony.mc.otm.graph.storage.StorageNode
import ru.dbotthepony.mc.otm.graph.storage.StorageGraph import ru.dbotthepony.mc.otm.graph.storage.StorageGraph
import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.container.set import ru.dbotthepony.mc.otm.container.set
import ru.dbotthepony.mc.otm.core.nbt.mapString import ru.dbotthepony.mc.otm.core.nbt.mapString
@ -41,122 +40,32 @@ import ru.dbotthepony.mc.otm.storage.*
import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent
import java.math.BigInteger import java.math.BigInteger
import java.util.* import java.util.*
import java.util.function.Supplier
import kotlin.collections.HashMap import kotlin.collections.HashMap
import ru.dbotthepony.mc.otm.client.render.Widgets8
import ru.dbotthepony.mc.otm.container.CombinedContainer
import ru.dbotthepony.mc.otm.container.addItem
import ru.dbotthepony.mc.otm.container.fullIterator
import ru.dbotthepony.mc.otm.container.iterator
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.collect.toList
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
class ItemMonitorPlayerSettings : INBTSerializable<CompoundTag>, MatteryPacket { interface IItemMonitorPlayerSettings {
enum class IngredientPriority(val component: Component) { var ingredientPriority: ItemMonitorPlayerSettings.IngredientPriority
// Refill everything from system var resultTarget: ItemMonitorPlayerSettings.ResultTarget
SYSTEM(TranslatableComponent("otm.gui.item_monitor.refill_source.system")), var craftingAmount: ItemMonitorPlayerSettings.Amount
var sorting: ItemStorageStackSorter
// Refill everything from player's inventory var ascendingSort: Boolean
INVENTORY(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory")),
// Refill everything from system, if can't refill from player's inventory
SYSTEM_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.system_first")),
// Refill everything from player's inventory, if can't refill from system
INVENTORY_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory_first")),
// Do not refill (?)
DO_NOT(TranslatableComponent("otm.gui.item_monitor.refill_source.do_not")),
}
enum class ResultTarget(val component: Component) {
// Everything goes into storage system
ALL_SYSTEM(TranslatableComponent("otm.gui.item_monitor.result_target.all_system")),
// Result goes to player inventory
// Crafting remainder goes to storage system
MIXED(TranslatableComponent("otm.gui.item_monitor.result_target.mixed")),
// Everything goes into player inventory
ALL_INVENTORY(TranslatableComponent("otm.gui.item_monitor.result_target.all_inventory")),
}
enum class Amount(val component: Component) {
ONE(TranslatableComponent("otm.gui.item_monitor.amount.one")),
STACK(TranslatableComponent("otm.gui.item_monitor.amount.stack")),
FULL(TranslatableComponent("otm.gui.item_monitor.amount.full"))
}
var ingredientPriority = IngredientPriority.SYSTEM
var resultTarget = ResultTarget.MIXED
var craftingAmount = Amount.STACK
override fun serializeNBT(): CompoundTag {
return CompoundTag().also {
it[INGREDIENT_PRIORITY_KEY] = ingredientPriority.name
it[RESULT_TARGET_KEY] = resultTarget.name
it[QUICK_CRAFT_AMOUNT_KEY] = craftingAmount.name
}
}
override fun deserializeNBT(nbt: CompoundTag) {
ingredientPriority = nbt.mapString(INGREDIENT_PRIORITY_KEY, IngredientPriority::valueOf, IngredientPriority.SYSTEM)
resultTarget = nbt.mapString(RESULT_TARGET_KEY, ResultTarget::valueOf, ResultTarget.MIXED)
craftingAmount = nbt.mapString(QUICK_CRAFT_AMOUNT_KEY, Amount::valueOf, Amount.STACK)
}
fun read(buff: FriendlyByteBuf) {
ingredientPriority = buff.readEnum(IngredientPriority::class.java)
resultTarget = buff.readEnum(ResultTarget::class.java)
craftingAmount = buff.readEnum(Amount::class.java)
}
fun read(other: ItemMonitorPlayerSettings) {
ingredientPriority = other.ingredientPriority
resultTarget = other.resultTarget
craftingAmount = other.craftingAmount
}
override fun write(buff: FriendlyByteBuf) {
buff.writeEnum(ingredientPriority)
buff.writeEnum(resultTarget)
buff.writeEnum(craftingAmount)
}
private fun playClient() {
val ply = minecraft.player ?: throw IllegalStateException("Player is missing")
val container = ply.containerMenu as? ItemMonitorMenu ?: return LOGGER.error("Received ItemMonitorPlayerSettings but container is missing or not of right type ({})!", ply.containerMenu)
container.settings.read(this)
}
// spectators are allowed because they change their own setting which is invisible to everyone else
private fun playServer(ply: ServerPlayer) {
val container = ply.containerMenu as? ItemMonitorMenu ?: return LOGGER.error("Received ItemMonitorPlayerSettings from {} but container is missing or not of right type ({})!", ply, ply.containerMenu)
container.settings.read(this)
(container.tile as ItemMonitorBlockEntity).setChangedLight()
}
override fun play(context: Supplier<NetworkEvent.Context>) {
context.get().packetHandled = true
val ply = context.get().sender
if (ply != null) {
playServer(ply)
} else {
playClient()
}
}
companion object {
fun read(buff: FriendlyByteBuf): ItemMonitorPlayerSettings {
return ItemMonitorPlayerSettings().also { it.read(buff) }
}
private val LOGGER = LogManager.getLogger()
const val INGREDIENT_PRIORITY_KEY = "ingredientPriority"
const val RESULT_TARGET_KEY = "resultTarget"
const val QUICK_CRAFT_AMOUNT_KEY = "quickCraftAmount"
}
} }
private fun takeOne(inventory: Inventory, item: ItemStack): Boolean { private fun takeOne(inventory: CombinedContainer?, item: ItemStack): Boolean {
for (i in 0 until inventory.containerSize) { val iterator = inventory?.optimizedIterator() ?: return false
if (!inventory[i].isEmpty && ItemStack.isSameItemSameTags(inventory[i], item)) {
inventory.removeItem(i, 1) for (stack in iterator) {
if (stack.equals(item, false)) {
stack.shrink(1)
inventory.setChanged()
return true return true
} }
} }
@ -164,21 +73,110 @@ private fun takeOne(inventory: Inventory, item: ItemStack): Boolean {
return false return false
} }
private fun takeOne(id: UUID?, view: IStorageProvider<ItemStorageStack>): Boolean { private fun takeOne(id: UUID?, view: IStorageProvider<ItemStorageStack>?): Boolean {
val extracted = view.extractStack(id ?: return false, BigInteger.ONE, false) return view?.extractStack(id ?: return false, BigInteger.ONE, false)?.isNotEmpty == true
}
if (!extracted.isEmpty) { class ItemMonitorPlayerSettings : INBTSerializable<CompoundTag>, IItemMonitorPlayerSettings {
return true interface Setting {
val component: Component
val icon: IGUIRenderable
val winding: UVWindingOrder
} }
return false enum class IngredientPriority(override val component: Component, icon: Lazy<IGUIRenderable>, override val winding: UVWindingOrder = UVWindingOrder.NORMAL) : Setting {
// Refill everything from system
SYSTEM(TranslatableComponent("otm.gui.item_monitor.refill_source.system"), lazy { Widgets8.WHITE_ARROW_DOWN }, UVWindingOrder.FLIP) {
override fun takeOne(item: ItemStack, view: IStorageProvider<ItemStorageStack>?, inventory: CombinedContainer?, id: UUID?): Boolean {
return takeOne(id, view)
}
},
// Refill everything from player's inventory
INVENTORY(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory"), lazy { Widgets8.WHITE_ARROW_DOWN }) {
override fun takeOne(item: ItemStack, view: IStorageProvider<ItemStorageStack>?, inventory: CombinedContainer?, id: UUID?): Boolean {
return takeOne(inventory, item)
}
},
// Refill everything from system, if can't refill from player's inventory
SYSTEM_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.system_first"), lazy { Widgets8.ARROW_SIDEWAYS }, UVWindingOrder.FLIP) {
override fun takeOne(item: ItemStack, view: IStorageProvider<ItemStorageStack>?, inventory: CombinedContainer?, id: UUID?): Boolean {
return takeOne(id, view) || takeOne(inventory, item)
}
},
// Refill everything from player's inventory, if can't refill from system
INVENTORY_FIRST(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory_first"), lazy { Widgets8.ARROW_SIDEWAYS }) {
override fun takeOne(item: ItemStack, view: IStorageProvider<ItemStorageStack>?, inventory: CombinedContainer?, id: UUID?): Boolean {
return takeOne(inventory, item) || takeOne(id, view)
}
},
// Do not refill (?)
DO_NOT(TranslatableComponent("otm.gui.item_monitor.refill_source.do_not"), lazy { Widgets8.MINUS }) {
override fun takeOne(item: ItemStack, view: IStorageProvider<ItemStorageStack>?, inventory: CombinedContainer?, id: UUID?): Boolean {
return false
}
};
override val icon: IGUIRenderable by icon
abstract fun takeOne(item: ItemStack, view: IStorageProvider<ItemStorageStack>?, inventory: CombinedContainer?, id: UUID?): Boolean
}
enum class ResultTarget(override val component: Component, icon: Lazy<IGUIRenderable>, override val winding: UVWindingOrder = UVWindingOrder.NORMAL) : Setting {
// Everything goes into storage system
ALL_SYSTEM(TranslatableComponent("otm.gui.item_monitor.result_target.all_system"), lazy { Widgets8.ARROW_PAINTED_UP }),
// Result goes to player inventory
// Crafting remainder goes to storage system
MIXED(TranslatableComponent("otm.gui.item_monitor.result_target.mixed"), lazy { Widgets8.ARROW_SIDEWAYS }),
// Everything goes into player inventory
ALL_INVENTORY(TranslatableComponent("otm.gui.item_monitor.result_target.all_inventory"), lazy { Widgets8.ARROW_PAINTED_UP }, UVWindingOrder.FLIP);
override val icon: IGUIRenderable by icon
}
enum class Amount(override val component: Component, icon: Lazy<IGUIRenderable>, override val winding: UVWindingOrder = UVWindingOrder.NORMAL) : Setting {
ONE(TranslatableComponent("otm.gui.item_monitor.amount.one"), lazy { Widgets8.ONE }),
STACK(TranslatableComponent("otm.gui.item_monitor.amount.stack"), lazy { Widgets8.S }),
FULL(TranslatableComponent("otm.gui.item_monitor.amount.full"), lazy { Widgets8.F });
override val icon: IGUIRenderable by icon
}
override var ingredientPriority = IngredientPriority.SYSTEM
override var resultTarget = ResultTarget.MIXED
override var craftingAmount = Amount.STACK
override var sorting: ItemStorageStackSorter = ItemStorageStackSorter.DEFAULT
override var ascendingSort: Boolean = false
override fun serializeNBT(): CompoundTag {
return CompoundTag().also {
it["ingredientPriority"] = ingredientPriority.name
it["resultTarget"] = resultTarget.name
it["quickCraftAmount"] = craftingAmount.name
it["sorting"] = sorting.name
it["ascendingSort"] = ascendingSort
}
}
override fun deserializeNBT(nbt: CompoundTag) {
ingredientPriority = nbt.mapString("ingredientPriority", IngredientPriority::valueOf, IngredientPriority.SYSTEM)
resultTarget = nbt.mapString("resultTarget", ResultTarget::valueOf, ResultTarget.MIXED)
craftingAmount = nbt.mapString("quickCraftAmount", Amount::valueOf, Amount.STACK)
sorting = nbt.mapString("sorting", ItemStorageStackSorter::valueOf, ItemStorageStackSorter.DEFAULT)
ascendingSort = nbt.getBoolean("ascendingSort")
}
} }
class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.ITEM_MONITOR, blockPos, blockState), IStorageEventConsumer<ItemStorageStack> { class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryPoweredBlockEntity(MBlockEntities.ITEM_MONITOR, blockPos, blockState), IStorageEventConsumer<ItemStorageStack> {
val energy = WorkerEnergyStorage(this::setChangedLight, MachinesConfig.ITEM_MONITOR) val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::setChangedLight, MachinesConfig.ITEM_MONITOR))
val energyConfig = ConfigurableEnergy(energy)
init { init {
exposeEnergyGlobally(energy)
savetable(::energy, ENERGY_KEY) savetable(::energy, ENERGY_KEY)
} }
@ -203,79 +201,74 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
} }
private val settings = HashMap<UUID, ItemMonitorPlayerSettings>() private val settings = HashMap<UUID, ItemMonitorPlayerSettings>()
private val craftingAmount = Object2ObjectArrayMap<Player, Int>() private val craftingAmount = Object2IntOpenHashMap<Player>()
private val lastCraftingRecipe = Object2ObjectArrayMap<Player, CraftingRecipe>() private val lastCraftingRecipe = Object2ObjectArrayMap<Player, CraftingRecipe>()
private var inProcessOfCraft = false
private val craftingGridTuples = arrayOfNulls<UUID>(3 * 3)
fun howMuchPlayerCrafted(ply: Player): Int = craftingAmount[ply] ?: 0 fun howMuchPlayerCrafted(ply: Player): Int = craftingAmount.getInt(ply)
fun lastCraftingRecipe(ply: Player) = lastCraftingRecipe[ply] fun lastCraftingRecipe(ply: Player) = lastCraftingRecipe[ply]
// a lot of code is hardcoded to take CraftingContainer as it's input // a lot of code is hardcoded to take CraftingContainer as it's input
// hence we are forced to work around this by providing proxy container // hence we are forced to work around this by providing proxy container
val craftingGrid = object : MatteryContainer(::setChangedLight, 3 * 3) { val craftingGrid = object : MatteryContainer(3 * 3) {
override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) { override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
super.setChanged(slot, new, old) setChangedLight()
craftingGridDummy[slot] = new craftingGridVanilla[slot] = new
if (!inProcessOfCraft) if (!inProcessOfCraft) {
scanCraftingGrid() scanCraftingGrid(false)
}
} }
}.also(::addDroppableContainer) }.also(::addDroppableContainer)
private var inProcessOfCraft = false private val craftingGridVanilla = TransientCraftingContainer(object : AbstractContainerMenu(null, Int.MIN_VALUE) {
override fun stillValid(p_38874_: Player) = true
override fun quickMoveStack(p_38941_: Player, p_38942_: Int) = ItemStack.EMPTY
}, 3, 3)
private fun scanCraftingGrid() { private fun scanCraftingGrid(justCheckForRecipeChange: Boolean): Boolean {
val server = level?.server ?: return val level = level ?: return false
val oldRecipe = craftingRecipe val server = level.server ?: return false
if (oldRecipe != null && oldRecipe.matches(craftingGridDummy, level!!)) { var craftingRecipe = craftingRecipe
return if (craftingRecipe != null && craftingRecipe.matches(craftingGridVanilla, level)) return true
} if (justCheckForRecipeChange) return false
craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGridDummy, level!!).orElse(null) craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGridVanilla, level).orElse(null)
val craftingRecipe = craftingRecipe Arrays.fill(craftingGridTuples, null)
val poweredView = poweredView
if (oldRecipe != craftingRecipe) { if (craftingRecipe != null && poweredView != null) {
Arrays.fill(craftingGridTuples, null) for (i in craftingGridTuples.indices) {
val item = craftingGrid[i]
val poweredView = poweredView if (!item.isEmpty) {
craftingGridTuples[i] = poweredView[ItemStorageStack.unsafe(item)]
if (craftingRecipe != null && poweredView != null) {
for (i in craftingGridTuples.indices) {
val item = craftingGrid[i]
if (!item.isEmpty) {
craftingGridTuples[i] = poweredView[ItemStorageStack.unsafe(item)]
}
} }
} }
} }
this.craftingRecipe = craftingRecipe
return false
} }
private val craftingGridTuples = arrayOfNulls<UUID>(3 * 3)
private val craftingMenu = object : AbstractContainerMenu(null, Int.MIN_VALUE) {
override fun stillValid(p_38874_: Player): Boolean {
return true
}
override fun quickMoveStack(p_38941_: Player, p_38942_: Int): ItemStack {
return ItemStack.EMPTY
}
}
private val craftingGridDummy = TransientCraftingContainer(craftingMenu, 3, 3)
override val storageType: StorageStack.Type<ItemStorageStack> override val storageType: StorageStack.Type<ItemStorageStack>
get() = StorageStack.ITEMS get() = StorageStack.ITEMS
override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider<ItemStorageStack>) { override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider<ItemStorageStack>) {
// no op if (craftingRecipe != null) {
} for (i in craftingGridTuples.indices) {
val item = craftingGrid[i]
override fun onStackChanged(stack: ItemStorageStack, id: UUID) {
// no op if (!item.isEmpty && stack.equalsWithoutCount(ItemStorageStack.unsafe(item))) {
craftingGridTuples[i] = id
}
}
}
} }
override fun onStackChanged(stack: ItemStorageStack, id: UUID) {}
override fun onStackRemoved(id: UUID) { override fun onStackRemoved(id: UUID) {
for (i in craftingGridTuples.indices) { for (i in craftingGridTuples.indices) {
if (craftingGridTuples[i] == id) { if (craftingGridTuples[i] == id) {
@ -300,137 +293,112 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
override fun getContainerSize() = 1 override fun getContainerSize() = 1
override fun isEmpty() = craftingRecipe == null override fun isEmpty() = craftingRecipe == null
override fun stillValid(p_18946_: Player) = true override fun stillValid(player: Player) = true
override fun getItem(p_18941_: Int): ItemStack { override fun getItem(slot: Int): ItemStack {
require(p_18941_ == 0) { "Invalid slot ID: $p_18941_" } require(slot == 0) { "Invalid slot: $slot" }
return craftingRecipe?.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY)?.copy() ?: ItemStack.EMPTY return craftingRecipe?.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY)?.copy() ?: ItemStack.EMPTY
} }
override fun removeItem(index: Int, amount: Int): ItemStack { override fun removeItem(index: Int, amount: Int): ItemStack {
require(index == 0) { "Invalid index $index" } require(index == 0) { "Invalid index $index" }
val craftingRecipe = craftingRecipe val level = level ?: return ItemStack.EMPTY
val craftingPlayer = craftingPlayer val craftingRecipe = craftingRecipe ?: return ItemStack.EMPTY
val craftingPlayer = craftingPlayer ?: return ItemStack.EMPTY
if (craftingRecipe == null || craftingPlayer == null || craftingRecipe.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY).count != amount) { try {
return ItemStack.EMPTY ForgeHooks.setCraftingPlayer(craftingPlayer)
if (craftingRecipe.getResultItem(level.registryAccess()).count != amount) {
return ItemStack.EMPTY
}
} finally {
ForgeHooks.setCraftingPlayer(null)
} }
var crafts = craftingAmount[craftingPlayer] ?: 0 craftingAmount[craftingPlayer] = craftingAmount.getInt(craftingPlayer) + 1
crafts++
craftingAmount[craftingPlayer] = crafts
lastCraftingRecipe[craftingPlayer] = craftingRecipe lastCraftingRecipe[craftingPlayer] = craftingRecipe
tickList.namedTimer(craftingPlayer, 4) {
craftingAmount.removeInt(craftingPlayer)
lastCraftingRecipe.remove(craftingPlayer)
}
val settings = getSettings(craftingPlayer) val settings = getSettings(craftingPlayer)
inProcessOfCraft = true inProcessOfCraft = true
try { try {
ForgeHooks.setCraftingPlayer(craftingPlayer) ForgeHooks.setCraftingPlayer(craftingPlayer)
val remainingItems: NonNullList<ItemStack> = craftingRecipe.getRemainingItems(craftingGridDummy) val residue: NonNullList<ItemStack>
ForgeHooks.setCraftingPlayer(null) val result: ItemStack
check(remainingItems.size == craftingGrid.containerSize) { "${remainingItems.size} != ${craftingGrid.containerSize} !!!" } try {
residue = craftingRecipe.getRemainingItems(craftingGridVanilla)
result = craftingRecipe.getResultItem(level.registryAccess())
} finally {
ForgeHooks.setCraftingPlayer(null)
}
check(residue.size == craftingGrid.containerSize) { "Container and residue list sizes mismatch: ${residue.size} != ${craftingGrid.containerSize}" }
val combinedInventory = craftingPlayer.matteryPlayer?.combinedInventory
val copy = craftingGrid.fullIterator().map { it.copy() }.toList()
// удаляем по одному предмету из сетки крафта
for (slot in 0 until craftingGrid.containerSize) { for (slot in 0 until craftingGrid.containerSize) {
val oldItem = craftingGrid[slot].let { if (!it.isEmpty || it.count < 2) it.copy() else it } craftingGrid.removeItem(slot, 1)
if (!craftingGrid[slot].isEmpty) { if (residue[slot].isNotEmpty) {
craftingGrid.removeItem(slot, 1) craftingGrid[slot] = residue[slot]
} }
}
var newItem = craftingGrid[slot] if (!scanCraftingGrid(true)) {
// заполняем опустевшие слоты
if (newItem.isEmpty) { for (slot in 0 until craftingGrid.containerSize) {
when (settings.ingredientPriority) { if (
ItemMonitorPlayerSettings.IngredientPriority.SYSTEM -> { craftingGrid[slot].isEmpty &&
if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) { copy[slot].isNotEmpty &&
newItem = oldItem settings.ingredientPriority.takeOne(copy[slot], poweredView, combinedInventory, craftingGridTuples[slot])
craftingGrid[slot] = newItem ) {
} craftingGrid[slot] = copy[slot].copyWithCount(1)
}
ItemMonitorPlayerSettings.IngredientPriority.INVENTORY -> {
if (takeOne(craftingPlayer.inventory, oldItem)) {
newItem = oldItem
craftingGrid[slot] = newItem
}
}
ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST -> {
if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) {
newItem = oldItem
craftingGrid[slot] = newItem
}
if (newItem.isEmpty && takeOne(craftingPlayer.inventory, oldItem)) {
newItem = oldItem
craftingGrid[slot] = newItem
}
}
ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST -> {
if (takeOne(craftingPlayer.inventory, oldItem)) {
newItem = oldItem
craftingGrid[slot] = newItem
} else if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) {
newItem = oldItem
craftingGrid[slot] = newItem
}
}
ItemMonitorPlayerSettings.IngredientPriority.DO_NOT -> { /* no op */ }
} }
} }
}
val remainder = remainingItems[slot] if (!scanCraftingGrid(true)) {
// избавляемся от остатков, если они мешают
for (slot in 0 until craftingGrid.containerSize) {
if (residue[slot].isNotEmpty) {
var remaining = residue[slot]
if (!remainder.isEmpty) { if (settings.resultTarget != ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY) {
when (settings.resultTarget) { remaining = poweredView?.insertStack(ItemStorageStack(remaining), false)?.toItemStack() ?: remaining
ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM, ItemMonitorPlayerSettings.ResultTarget.MIXED -> {
val remaining = poweredView?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder
if (!remaining.isEmpty) {
if (newItem.isEmpty) {
craftingGrid[slot] = remaining
} else if (ItemStack.isSameItemSameTags(newItem, remaining)) {
newItem.grow(remaining.count)
craftingGrid.setChanged(slot)
} else if (!craftingPlayer.inventory.add(remaining)) {
craftingPlayer.drop(remaining, false)
}
}
} }
ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY -> { remaining = combinedInventory?.addItem(remaining, false) ?: remaining
if (!craftingPlayer.inventory.add(remainder)) {
val remaining = poweredView?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder
if (!remaining.isEmpty) { if (remaining.isNotEmpty) {
if (newItem.isEmpty) { craftingPlayer.spawnAtLocation(remaining)
craftingGrid[slot] = remaining }
} else if (ItemStack.isSameItemSameTags(newItem, remaining)) {
newItem.grow(remaining.count) if (copy[slot].isNotEmpty && settings.ingredientPriority.takeOne(copy[slot], poweredView, combinedInventory, craftingGridTuples[slot])) {
craftingGrid.setChanged(slot) craftingGrid[slot] = copy[slot].copyWithCount(1)
} else {
craftingPlayer.drop(remaining, false)
}
}
}
} }
} }
} }
} }
return result.copy()
} finally { } finally {
inProcessOfCraft = false inProcessOfCraft = false
scanCraftingGrid() scanCraftingGrid(false)
} }
return craftingRecipe.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY).copy()
} }
override fun removeItemNoUpdate(p_18951_: Int): ItemStack { override fun removeItemNoUpdate(slot: Int): ItemStack {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
@ -464,11 +432,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
val settings = nbt.getCompound("player_settings") val settings = nbt.getCompound("player_settings")
for (key in settings.allKeys) { for (key in settings.allKeys) {
val uuid = UUID.fromString(key) check(this.settings.put(UUID.fromString(key), ItemMonitorPlayerSettings().also { it.deserializeNBT(settings.getCompound(key)) }) == null)
val deserialized = ItemMonitorPlayerSettings()
deserialized.deserializeNBT(settings.getCompound(key))
check(this.settings.put(uuid, deserialized) == null) { "Duplicate UUID??? $uuid" }
} }
craftingGrid.deserializeNBT(nbt["crafting_grid"]) craftingGrid.deserializeNBT(nbt["crafting_grid"])
@ -480,14 +444,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
override fun tick() { override fun tick() {
super.tick() super.tick()
cell.tickEnergyDemanding() cell.tickEnergyDemanding()
if (craftingAmount.size != 0)
craftingAmount.clear()
if (lastCraftingRecipe.size != 0)
lastCraftingRecipe.clear()
} }
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
@ -497,6 +454,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
override fun setLevel(level: Level) { override fun setLevel(level: Level) {
super.setLevel(level) super.setLevel(level)
cell.discover(this) cell.discover(this)
scanCraftingGrid(false)
} }
override fun setRemoved() { override fun setRemoved() {

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.block.entity.storage
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
@ -126,6 +127,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
private inner class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent<ItemStorageStack> { private inner class ItemHandlerComponent(private val parent: IItemHandler) : IStorageComponent<ItemStorageStack> {
private fun updateTuple(tuple: TrackedTuple, diff: BigInteger) { private fun updateTuple(tuple: TrackedTuple, diff: BigInteger) {
val stack = tuple.stack
tuple.stack = tuple.stack.grow(diff) tuple.stack = tuple.stack.grow(diff)
if (tuple.stack.isNotEmpty) { if (tuple.stack.isNotEmpty) {
@ -138,7 +140,7 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
} }
id2tuples.remove(tuple.id) id2tuples.remove(tuple.id)
items2tuples.remove(tuple.stack) ?: throw IllegalStateException("Cross-reference integrity check failed for $tuple") items2tuples.remove(stack) ?: throw IllegalStateException("Cross-reference integrity check failed for $tuple")
} }
} }
@ -376,29 +378,32 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
val tuple = id2tuples[id] ?: return ItemStorageStack.EMPTY val tuple = id2tuples[id] ?: return ItemStorageStack.EMPTY
val slots = tuple.children.values.iterator() val slots = tuple.children.values.iterator()
val lstack = tuple.stack val lstack = tuple.stack
val affectedSlots = IntArrayList()
while (amount < total && slots.hasNext() && energy.batteryLevel.isPositive) { while (amount > total && slots.hasNext() && energy.batteryLevel.isPositive) {
val (slot, stack) = slots.next() val (slot, stack) = slots.next()
val extracted = parent.extractItem(slot, stack.count.coerceAtMost(amount.toIntSafe()), true) val extracted = parent.extractItem(slot, stack.count.coerceAtMost((amount - total).toIntSafe()), true)
val required = StorageStack.ITEMS.energyPerExtract(ItemStorageStack.unsafe(extracted)) val required = StorageStack.ITEMS.energyPerExtract(ItemStorageStack.unsafe(extracted))
if (extracted.isNotEmpty && tuple.stack.equalsWithoutCount(ItemStorageStack.unsafe(extracted)) && energy.extractEnergy(required, true) == required) { if (extracted.isNotEmpty && lstack.equalsWithoutCount(ItemStorageStack.unsafe(extracted)) && energy.extractEnergy(required, true) == required) {
if (simulate) { if (simulate) {
total += extracted.count.toBigInteger() total += extracted.count.toBigInteger()
} else { } else {
affectedSlots.add(slot)
val extracted2 = parent.extractItem(slot, extracted.count, false) val extracted2 = parent.extractItem(slot, extracted.count, false)
total += extracted2.count.toBigInteger()
if (extracted2.count == extracted.count) { if (extracted2.count == extracted.count) {
energy.extractEnergy(required, false) energy.extractEnergy(required, false)
} else { } else {
energy.extractEnergy(StorageStack.ITEMS.energyPerExtract(ItemStorageStack.unsafe(extracted2)), false) energy.extractEnergy(StorageStack.ITEMS.energyPerExtract(ItemStorageStack.unsafe(extracted2)), false)
} }
total += extracted2.count.toBigInteger()
} }
} }
} }
val i = affectedSlots.intIterator()
while (i.hasNext()) scan(i.nextInt())
return lstack.copy(total) return lstack.copy(total)
} }

View File

@ -9,7 +9,6 @@ import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.AABB import net.minecraft.world.phys.AABB
import net.minecraftforge.common.ForgeConfigSpec
import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.block.entity.WorkerState
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
@ -18,13 +17,9 @@ import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.capability.moveEnergy import ru.dbotthepony.mc.otm.capability.moveEnergy
import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.DecimalConfigValue
import ru.dbotthepony.mc.otm.core.math.defineDecimal
import ru.dbotthepony.mc.otm.core.ifPresentK import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu import ru.dbotthepony.mc.otm.menu.tech.AndroidStationMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MNames
import ru.dbotthepony.mc.otm.core.util.WriteOnce
class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : class AndroidStationBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
MatteryPoweredBlockEntity(MBlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider { MatteryPoweredBlockEntity(MBlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider {

View File

@ -4,13 +4,9 @@ import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.common.ForgeConfigSpec
import net.minecraftforge.common.ForgeHooks import net.minecraftforge.common.ForgeHooks
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.energy.IEnergyStorage
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.block.entity.WorkerState import ru.dbotthepony.mc.otm.block.entity.WorkerState
import ru.dbotthepony.mc.otm.capability.* import ru.dbotthepony.mc.otm.capability.*
@ -19,14 +15,9 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.core.*
import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu import ru.dbotthepony.mc.otm.menu.tech.ChemicalGeneratorMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MNames
import ru.dbotthepony.mc.otm.core.util.WriteOnce
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.DecimalConfigValue
import ru.dbotthepony.mc.otm.core.math.defineDecimal
class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.CHEMICAL_GENERATOR, pos, state) { class ChemicalGeneratorBlockEntity(pos: BlockPos, state: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.CHEMICAL_GENERATOR, pos, state) {
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {

View File

@ -4,7 +4,6 @@ import net.minecraft.core.BlockPos
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.FlowDirection

View File

@ -1,7 +1,6 @@
package ru.dbotthepony.mc.otm.block.entity.tech package ru.dbotthepony.mc.otm.block.entity.tech
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.inventory.AbstractContainerMenu

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.capability.drive
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmOverloads
import java.util.UUID import java.util.UUID
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
@ -30,7 +31,7 @@ abstract class AbstractMatteryDrive<T : StorageStack<T>> @JvmOverloads construct
override var isDirty = false override var isDirty = false
set(value) { set(value) {
if (value != field && value && DrivePool.isLegalAccess()) { if (value != field && value) {
DrivePool.markDirty(uuid) DrivePool.markDirty(uuid)
} }
@ -43,6 +44,24 @@ abstract class AbstractMatteryDrive<T : StorageStack<T>> @JvmOverloads construct
override var storedCount: BigInteger = BigInteger.ZERO override var storedCount: BigInteger = BigInteger.ZERO
protected set protected set
protected val listeners = ObjectLinkedOpenHashSet<IStorageEventConsumer<T>>()
override fun addListener(listener: IStorageEventConsumer<T>): Boolean {
return listeners.add(listener)
}
override fun removeListener(listener: IStorageEventConsumer<T>): Boolean {
return listeners.remove(listener)
}
override fun equals(other: Any?): Boolean {
return other is AbstractMatteryDrive<*> && storageType == other.storageType && uuid == other.uuid
}
override fun hashCode(): Int {
return (uuid.hashCode() * 31) xor storageType.hashCode()
}
override fun insertStack(stack: T, simulate: Boolean): T { override fun insertStack(stack: T, simulate: Boolean): T {
val maxInsert = driveCapacity.minus(storedCount).coerceAtMost(stack.count) val maxInsert = driveCapacity.minus(storedCount).coerceAtMost(stack.count)
if (maxInsert <= BigInteger.ZERO) return stack if (maxInsert <= BigInteger.ZERO) return stack
@ -189,14 +208,4 @@ abstract class AbstractMatteryDrive<T : StorageStack<T>> @JvmOverloads construct
override val stacks: Stream<IStorageTuple<T>> get() { override val stacks: Stream<IStorageTuple<T>> get() {
return ArrayList<IStorageTuple<T>>(stack2tuples.size).also { it.addAll(stack2tuples.values) }.stream() return ArrayList<IStorageTuple<T>>(stack2tuples.size).also { it.addAll(stack2tuples.values) }.stream()
} }
protected val listeners = ObjectArraySet<IStorageEventConsumer<T>>()
override fun addListener(listener: IStorageEventConsumer<T>): Boolean {
return listeners.add(listener)
}
override fun removeListener(listener: IStorageEventConsumer<T>): Boolean {
return listeners.remove(listener)
}
} }

View File

@ -1,15 +1,13 @@
package ru.dbotthepony.mc.otm.capability.drive package ru.dbotthepony.mc.otm.capability.drive
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import java.util.UUID import java.util.UUID
import net.minecraft.ReportedException import net.minecraft.ReportedException
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtIo import net.minecraft.nbt.NbtIo
import net.minecraft.CrashReport import net.minecraft.CrashReport
import net.minecraft.world.level.storage.LevelResource
import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.LockSupport
import net.minecraftforge.event.TickEvent.ServerTickEvent
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.server.ServerAboutToStartEvent import net.minecraftforge.event.server.ServerAboutToStartEvent
import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.event.server.ServerStoppingEvent
import net.minecraftforge.event.level.LevelEvent import net.minecraftforge.event.level.LevelEvent
@ -19,51 +17,82 @@ import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import java.io.File import java.io.File
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.ArrayList import java.util.ArrayList
import java.util.concurrent.ConcurrentLinkedQueue
private class WeakDriveReference(drive: IMatteryDrive<*>) {
private var drive: IMatteryDrive<*>? = drive
private val weak = WeakReference(drive)
val isValid: Boolean get() {
return drive != null || weak.get() != null
}
fun drive(): IMatteryDrive<*>? {
return drive ?: weak.get()
}
fun sync(): CompoundTag? {
val drive = drive() ?: return null
if (!drive.isDirty) {
this.drive = null
return null
}
val tag = drive.serializeNBT()
drive.isDirty = false
this.drive = null
return tag
}
fun access(): IMatteryDrive<*>? {
this.drive = weak.get()
return drive
}
}
private data class BacklogLine(val uuid: UUID, val tag: CompoundTag)
/** /**
* Let me answer everyone's questions about: * Separate thread and separate files are way faster for enormous volumes of data (if mod is being run on public dedicated server)
*
* Why?
*
* There are several reasons:
* 1. This data can get very large very quickly, even when playing singleplayer (and much quicker and bigger when hosting a dedicated server)
* 2. This data can not be stored inside ItemStack.ForgeCaps due to it's size
* 3. This data can not be stored inside unshared (server only) nbt tag because, again, mods prone to use and interact with
* it wrong, causing loss of stored data or mods exposing full content of a drive inside their own tag (which cause very real "NBT size too large"
* network kicks, often locking players out of server/singleplayer worlds
* 4. [net.minecraft.world.level.saveddata.SavedData] is for storing everything inside one dat file, which
* is performance tanking, because we have to write *entire* NBT on each save, not the data of Drives that are dirty
* 5. Mods which check items for being stack-able even with stack size of 1 gonna compare nbt tag,
* which will be performance tanking due to clause 1.
*/ */
object DrivePool { object DrivePool {
private val resource = LevelResource("otm_drives")
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
private val pool = Object2ObjectOpenHashMap<UUID, WeakDriveReference>() private val pool = Object2ObjectOpenHashMap<UUID, WeakDriveReference>()
private var thread: Thread? = null private var thread: Thread? = null
private var stopping = false private var stopping = false
private var exception: ReportedException? = null
private const val DATA_PATH = "data/otm_drives"
@JvmStatic private val backlog = ConcurrentLinkedQueue<BacklogLine>()
operator fun <T : IMatteryDrive<*>> get( private var knownBaseDirectory: File? = null
id: UUID,
deserializer: (CompoundTag) -> T, private var serverThread: Thread? = null
factory: () -> T
): T { private val baseDirectory: File? get() {
val server = NULLABLE_MINECRAFT_SERVER ?: return null
val baseDirectory = server.storageSource.getLevelPath(resource).toFile()
if (knownBaseDirectory != baseDirectory) {
baseDirectory.mkdirs()
knownBaseDirectory = baseDirectory
}
return baseDirectory
}
operator fun <T : IMatteryDrive<*>> get(id: UUID, deserializer: (CompoundTag) -> T, factory: () -> T): T {
if (!isLegalAccess()) if (!isLegalAccess())
throw IllegalAccessException("Can not access drive pool from outside of server thread.") throw IllegalAccessException("Can not access drive pool from outside of server thread.")
if (!SERVER_IS_LIVE) if (!SERVER_IS_LIVE)
throw IllegalStateException("Fail-fast: Server is shutting down") throw IllegalStateException("Server is not running")
val get = pool[id] val get = pool[id]?.access()
if (get != null) { if (get != null) {
val accessed = get.access() return get as T
if (accessed != null) {
return accessed as T
}
} }
val file = File(baseDirectory, "$id.dat") val file = File(baseDirectory, "$id.dat")
@ -85,69 +114,9 @@ object DrivePool {
return factory().also { pool[id] = WeakDriveReference(it) } return factory().also { pool[id] = WeakDriveReference(it) }
} }
@JvmStatic
fun put(id: UUID, drive: IMatteryDrive<*>) {
if (!isLegalAccess())
throw IllegalAccessException("Can not access drive pool from outside of server thread.")
if (!SERVER_IS_LIVE)
throw IllegalStateException("Fail-fast: Server is shutting down")
pool[id] = WeakDriveReference(drive)
}
@JvmStatic
fun markDirty(id: UUID) { fun markDirty(id: UUID) {
if (!isLegalAccess()) if (isLegalAccess()) {
throw IllegalAccessException("Can not access drive pool from outside of server thread.") pool[id]?.access()
if (!SERVER_IS_LIVE)
throw IllegalStateException("Fail-fast: Server is shutting down")
pool[id]?.access()
}
private var backlog = ArrayList<BacklogLine>()
private var knownBaseDirectory: File? = null
private val baseDirectory: File? get() {
val server = NULLABLE_MINECRAFT_SERVER ?: return null
val baseDirectory = File(server.storageSource.worldDir.toFile(), DATA_PATH)
if (knownBaseDirectory != baseDirectory) {
baseDirectory.mkdirs()
knownBaseDirectory = baseDirectory
}
return baseDirectory
}
private var serverThread: Thread? = null
private fun thread() {
while (true) {
LockSupport.park()
if (stopping) {
return
}
try {
// TODO: Syncing status with main thread, since server stop can occur during work.
sync()
} catch (error: ReportedException) {
exception = error
return
}
}
}
fun onServerPostTick(event: ServerTickEvent) {
if (event.phase == TickEvent.Phase.END) {
if (exception != null) {
throw exception!!
}
} }
} }
@ -156,12 +125,8 @@ object DrivePool {
* *
* If you are making your own drive for your own storage stack, feed code outside of server thread (e.g. client code) dummy disk. * If you are making your own drive for your own storage stack, feed code outside of server thread (e.g. client code) dummy disk.
*/ */
@JvmStatic
fun isLegalAccess(): Boolean { fun isLegalAccess(): Boolean {
if (serverThread == null) return serverThread != null && Thread.currentThread() === serverThread
return false
return Thread.currentThread() === serverThread
} }
fun serverStartEvent(event: ServerAboutToStartEvent) { fun serverStartEvent(event: ServerAboutToStartEvent) {
@ -175,7 +140,7 @@ object DrivePool {
serverThread = Thread.currentThread() serverThread = Thread.currentThread()
stopping = false stopping = false
thread = Thread(null, this::thread, "Overdrive That Matters DrivePool IO").also { it.start() } thread = Thread(null, this::run, "Overdrive That Matters DrivePool IO").also { it.start() }
} }
fun serverStopEvent(event: ServerStoppingEvent) { fun serverStopEvent(event: ServerStoppingEvent) {
@ -186,11 +151,8 @@ object DrivePool {
LockSupport.unpark(thread) LockSupport.unpark(thread)
} }
if (exception == null) { writeBacklog()
writeBacklog() sync()
sync()
}
pool.clear() pool.clear()
} }
@ -200,34 +162,26 @@ object DrivePool {
private fun writeBacklog() { private fun writeBacklog() {
var needsSync = false var needsSync = false
var removeKeys: ArrayList<UUID>? = null val removeKeys = ArrayList<UUID>()
for ((key, value) in pool) { for ((key, value) in pool) {
val tag = value.sync() try {
val tag = value.sync()
if (tag != null) { if (tag != null) {
LOGGER.info("Serializing OTM Drive {}", key) LOGGER.debug("Serializing OTM Drive {}", key)
val index = backlog.indexOf<Any>(key) backlog.add(BacklogLine(key, tag))
needsSync = true
if (index != -1) { } else if (!value.isValid) {
backlog[index] = BacklogLine(key, tag, value.drive()!!) removeKeys.add(key)
} else {
backlog.add(BacklogLine(key, tag, value.drive()!!))
} }
} catch (err: Throwable) {
needsSync = true LOGGER.error("Failed to serialize OTM Drive for persistent storage with ID $key", err)
} else if (!value.valid()) {
if (removeKeys == null)
removeKeys = ArrayList()
removeKeys.add(key)
} }
} }
if (removeKeys != null) { for (key in removeKeys) {
for (key in removeKeys) { pool.remove(key)
pool.remove(key)
}
} }
if (needsSync) { if (needsSync) {
@ -235,90 +189,31 @@ object DrivePool {
} }
} }
private fun run() {
while (!stopping) {
sync()
LockSupport.park()
}
}
private fun sync() { private fun sync() {
val copy = backlog while (true) {
backlog = ArrayList() val next = backlog.poll() ?: return
val (uuid, tag) = next
for (entry in copy) {
try { try {
LOGGER.info("Syncing OTM Drive {}", entry.file) LOGGER.debug("Syncing OTM Drive {}", uuid)
val oldFile = File(baseDirectory, entry.file.toString() + ".dat_old") val oldFile = File(baseDirectory, "$uuid.dat_old")
val newFile = File(baseDirectory, "$uuid.dat")
if (oldFile.exists()) { if (oldFile.exists()) check(oldFile.delete()) { "Unable to delete old dat file" }
check(oldFile.delete()) { "Unable to delete old dat file" } if (newFile.exists()) check(newFile.renameTo(oldFile)) { "Unable to move old dat file" }
}
val newFile = File(baseDirectory, entry.file.toString() + ".dat") NbtIo.writeCompressed(tag, newFile)
if (newFile.exists()) {
check(newFile.renameTo(oldFile)) { "Unable to move old dat file" }
}
NbtIo.writeCompressed(entry.tag, newFile)
} catch (error: Throwable) { } catch (error: Throwable) {
val report = CrashReport("Syncing OTM Drives state to disk", error) LOGGER.error("Failed to sync OTM Drive to persistent storage with ID $uuid", error)
val category = report.addCategory("Corrupt drive")
category.setDetail("UUID", entry.file.toString())
category.setDetail("Stored item count", entry.drive.storedCount)
category.setDetail("Capacity", entry.drive.driveCapacity)
throw ReportedException(report)
} }
} }
} }
} }
private class WeakDriveReference(drive: IMatteryDrive<*>) {
private var drive: IMatteryDrive<*>? = drive
private val weak = WeakReference(drive)
fun valid(): Boolean {
return drive != null || weak.get() != null
}
fun drive(): IMatteryDrive<*>? {
return drive ?: weak.get()
}
fun sync(): CompoundTag? {
var drive = drive
if (drive == null) {
drive = weak.get()
if (drive == null)
return null
}
if (!drive.isDirty) {
this.drive = null
return null
}
val tag = drive.serializeNBT()
drive.isDirty = false
this.drive = null
return tag
}
fun access(): IMatteryDrive<*>? {
this.drive = weak.get()
return drive
}
}
@Suppress("EqualsOrHashCode")
private data class BacklogLine(val file: UUID, val tag: CompoundTag, val drive: IMatteryDrive<*>) {
override fun equals(other: Any?): Boolean {
if (other is BacklogLine) {
return other.file == file
}
if (other is UUID) {
return other == file
}
return false
}
}

View File

@ -54,27 +54,10 @@ class MatterPanelScreen(
val controls = DeviceControls(this, frame) val controls = DeviceControls(this, frame)
LargeBooleanRectangleButtonPanel( controls.sortingButtons(menu.isAscendingGS, menu.sortingGS, ItemSorter.DEFAULT) {
this, for (v in ItemSorter.entries) {
controls, add(v, skinElement = v.icon, tooltip = v.title)
prop = menu.isAscendingGS, }
skinElementActive = Widgets18.ARROW_UP,
skinElementInactive = Widgets18.ARROW_DOWN,
tooltipActive = TranslatableComponent("otm.gui.sorting.ascending"),
tooltipInactive = TranslatableComponent("otm.gui.sorting.descending"),
).also {
controls.addButton(it)
}
LargeEnumRectangleButtonPanel(this, controls, enum = ItemSorter::class.java, prop = menu.sortingGS, defaultValue = ItemSorter.DEFAULT).also {
controls.addButton(it)
it.add(ItemSorter.DEFAULT, skinElement = Widgets18.SORT_DEFAULT, tooltip = ItemSorter.DEFAULT.title)
it.add(ItemSorter.NAME, skinElement = Widgets18.SORT_ALPHABET, tooltip = ItemSorter.NAME.title)
it.add(ItemSorter.ID, skinElement = Widgets18.SORT_ID, tooltip = ItemSorter.ID.title)
it.add(ItemSorter.MOD, skinElement = Widgets18.SORT_MODID, tooltip = ItemSorter.MOD.title)
it.add(ItemSorter.MATTER_VALUE, skinElement = Widgets18.SORT_MATTER_VALUE, tooltip = ItemSorter.MATTER_VALUE.title)
it.add(ItemSorter.MATTER_COMPLEXITY, skinElement = Widgets18.SORT_MATTER_COMPLEXITY, tooltip = ItemSorter.MATTER_COMPLEXITY.title)
it.finish()
} }
val scrollBar = DiscreteScrollBarPanel(this, frame, { val scrollBar = DiscreteScrollBarPanel(this, frame, {
@ -156,7 +139,7 @@ class MatterPanelScreen(
} }
} }
override fun getItemStackTooltip(stack: ItemStack): List<Component> { override fun getItemStackTooltip(stack: ItemStack): MutableList<Component> {
val list = super.getItemStackTooltip(stack).toMutableList() val list = super.getItemStackTooltip(stack).toMutableList()
if (isPatternView) { if (isPatternView) {
@ -326,7 +309,7 @@ class MatterPanelScreen(
return pattern.stack() return pattern.stack()
} }
override fun getItemStackTooltip(stack: ItemStack): List<Component> { override fun getItemStackTooltip(stack: ItemStack): MutableList<Component> {
return super.getItemStackTooltip(stack).toMutableList().also { return super.getItemStackTooltip(stack).toMutableList().also {
it.add(TranslatableComponent( it.add(TranslatableComponent(
"otm.item.pattern.research", "otm.item.pattern.research",

View File

@ -31,8 +31,26 @@ open class FramePanel<out S : Screen>(
var onOpen: Runnable? = null, var onOpen: Runnable? = null,
var onClose: Runnable? = null, var onClose: Runnable? = null,
var activeIcon: IGUIRenderable? = null, var activeIcon: IGUIRenderable? = null,
var inactiveIcon: IGUIRenderable? = null, var inactiveIcon: IGUIRenderable? = activeIcon,
) : AbstractButtonPanel<S>(this@FramePanel.screen, this@FramePanel, 0f, 0f, 26f, 28f) { ) : AbstractButtonPanel<S>(this@FramePanel.screen, this@FramePanel, 0f, 0f, 26f, 28f) {
constructor(panels: List<EditablePanel<*>>, activeIcon: IGUIRenderable? = null, inactiveIcon: IGUIRenderable? = activeIcon) : this(activeIcon = activeIcon, inactiveIcon = inactiveIcon) {
onOpen = Runnable {
for (panel in panels) {
panel.visible = true
}
}
onClose = Runnable {
for (panel in panels) {
panel.visible = false
}
}
if (!isActive) {
onClose!!.run()
}
}
var isActive = tabs.isEmpty() var isActive = tabs.isEmpty()
protected set protected set

View File

@ -0,0 +1,110 @@
package ru.dbotthepony.mc.otm.client.screen.panels
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.client.render.RenderGravity
import ru.dbotthepony.mc.otm.client.render.draw
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollBarConstants
import ru.dbotthepony.mc.otm.client.screen.storage.ItemMonitorScreen
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.integerDivisionDown
import ru.dbotthepony.mc.otm.core.util.formatReadableNumber
import ru.dbotthepony.mc.otm.core.util.formatSiComponent
import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView
import ru.dbotthepony.mc.otm.storage.StorageStack
open class NetworkedItemGridPanel<out S : MatteryScreen<*>>(
screen: S,
parent: EditablePanel<*>,
val view: NetworkedItemView,
x: Float = 0f,
y: Float = 0f,
width: Float = 40f,
height: Float = 40f,
) : EditablePanel<S>(screen, parent, x, y, width, height) {
constructor(
screen: S,
parent: EditablePanel<*>,
view: NetworkedItemView,
x: Float = 0f,
y: Float = 0f,
width: Int,
height: Int
) : this(screen, parent, view, x, y, width * AbstractSlotPanel.SIZE + ScrollBarConstants.WIDTH, height * AbstractSlotPanel.SIZE)
private val slots = ArrayList<Slot>()
val canvas = object : GridPanel<S>(screen, this@NetworkedItemGridPanel, 0f, 0f, 0f, 0f, 0, 0) {
override fun performLayout() {
super.performLayout()
val count = columns * rows
while (slots.size < count) {
slots.add(Slot(slots.size))
}
while (slots.size > count) {
slots.removeLast().remove()
}
}
}
val scrollbar = DiscreteScrollBarPanel(
screen,
this,
{ integerDivisionDown(view.itemCount, canvas.columns) },
{ _, _, _ -> }
)
inner class Slot(private val i: Int) : AbstractSlotPanel<S>(screen, canvas) {
private val index get() = i + scrollbar.scroll * canvas.columns
override val itemStack: ItemStack get() {
return view.sortedView.getOrNull(index)?.stack?.toItemStack() ?: ItemStack.EMPTY
}
override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean {
return scrollbar.mouseScrolledInner(x, y, scroll)
}
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
view.mouseClick(index, button)
return true
}
override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
renderSlotBackground(graphics, mouseX, mouseY, partialTick)
val itemStack = view.sortedView.getOrNull(index)?.stack ?: StorageStack.ITEMS.empty
renderRegular(graphics, itemStack.toItemStack(), "")
if (!itemStack.isEmpty) {
graphics.draw(font, itemStack.count.formatSiComponent(decimalPlaces = 1), x = width - 1f, y = height - 1f, gravity = RenderGravity.BOTTOM_RIGHT, scale = 0.75f, drawShadow = true)
}
}
override fun getItemStackTooltip(stack: ItemStack): MutableList<Component> {
return super.getItemStackTooltip(stack).also {
it.add(TranslatableComponent("otm.gui.stored_amount", view.sortedView[index].stack.count.formatReadableNumber()).withStyle(ChatFormatting.DARK_GRAY))
}
}
}
init {
canvas.dock = Dock.FILL
scrollbar.dock = Dock.RIGHT
}
override fun performLayout() {
super.performLayout()
canvas.rows = (canvas.height / AbstractSlotPanel.SIZE).toInt()
canvas.columns = (canvas.width / AbstractSlotPanel.SIZE).toInt()
}
}

View File

@ -29,6 +29,7 @@ import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.util.ItemSorter
import ru.dbotthepony.mc.otm.core.value import ru.dbotthepony.mc.otm.core.value
import ru.dbotthepony.mc.otm.menu.UpgradeSlots import ru.dbotthepony.mc.otm.menu.UpgradeSlots
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
@ -315,7 +316,6 @@ class DeviceControls<out S : MatteryScreen<*>>(
val upgradesButton: LargeRectangleButtonPanel<S>? val upgradesButton: LargeRectangleButtonPanel<S>?
private var upgradeWindow: FramePanel<S>? = null private var upgradeWindow: FramePanel<S>? = null
private var nextY = 0f private var nextY = 0f
fun <P : EditablePanel<@UnsafeVariance S>> addButton(button: P): P { fun <P : EditablePanel<@UnsafeVariance S>> addButton(button: P): P {
@ -328,6 +328,40 @@ class DeviceControls<out S : MatteryScreen<*>>(
return button return button
} }
fun <P : EditablePanel<@UnsafeVariance S>> prependButton(button: P): P {
for (child in children) {
child.y += button.height + 2f
}
button.parent = this
button.x = 0f
button.y = 0f
nextY += button.height + 2f
height = nextY - 2f
width = button.width.coerceAtLeast(width)
return button
}
inline fun <reified T : Enum<T>> sortingButtons(ascending: GetterSetter<Boolean>, sorting: GetterSetter<T>, default: T, configurator: LargeEnumRectangleButtonPanel<S, T>.() -> Unit) {
LargeBooleanRectangleButtonPanel(
screen,
this,
prop = ascending,
skinElementActive = Widgets18.ARROW_UP,
skinElementInactive = Widgets18.ARROW_DOWN,
tooltipActive = TranslatableComponent("otm.gui.sorting.ascending"),
tooltipInactive = TranslatableComponent("otm.gui.sorting.descending"),
).also {
prependButton(it)
}
LargeEnumRectangleButtonPanel(screen, this, enum = T::class.java, prop = sorting, defaultValue = default).also {
prependButton(it)
configurator.invoke(it)
it.finish()
}
}
init { init {
for (button in extra) { for (button in extra) {
addButton(button) addButton(button)

View File

@ -42,7 +42,7 @@ abstract class AbstractSlotPanel<out S : MatteryScreen<*>> @JvmOverloads constru
} }
} }
protected open fun getItemStackTooltip(stack: ItemStack): List<Component> { protected open fun getItemStackTooltip(stack: ItemStack): MutableList<Component> {
return getTooltipFromItem(ru.dbotthepony.mc.otm.client.minecraft, stack) return getTooltipFromItem(ru.dbotthepony.mc.otm.client.minecraft, stack)
} }

View File

@ -4,16 +4,28 @@ import net.minecraft.client.gui.screens.Screen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock import ru.dbotthepony.mc.otm.client.screen.panels.Dock
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
open class GridPanel<out S : Screen> @JvmOverloads constructor( open class GridPanel<out S : Screen>(
screen: S, screen: S,
parent: EditablePanel<*>?, parent: EditablePanel<*>?,
x: Float = 0f, x: Float = 0f,
y: Float = 0f, y: Float = 0f,
width: Float, width: Float = 0f,
height: Float, height: Float = 0f,
protected var columns: Int, columns: Int,
protected var rows: Int rows: Int
) : EditablePanel<S>(screen, parent, x, y, width, height) { ) : EditablePanel<S>(screen, parent, x, y, width, height) {
var columns: Int = columns
set(value) {
field = value
invalidateLayout()
}
var rows: Int = rows
set(value) {
field = value
invalidateLayout()
}
override fun performLayout() { override fun performLayout() {
var currentX = 0f var currentX = 0f
var currentY = 0f var currentY = 0f

View File

@ -1,23 +1,26 @@
package ru.dbotthepony.mc.otm.client.screen.storage package ru.dbotthepony.mc.otm.client.screen.storage
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import ru.dbotthepony.mc.otm.client.render.ItemStackIcon
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.client.screen.panels.* import ru.dbotthepony.mc.otm.client.screen.panels.*
import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilterSlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilterSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel
import ru.dbotthepony.mc.otm.core.math.integerDivisionDown import ru.dbotthepony.mc.otm.core.asGetterSetter
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem
import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu
import ru.dbotthepony.mc.otm.registry.MItems
import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak
@MouseTweaksDisableWheelTweak @MouseTweaksDisableWheelTweak
@ -25,106 +28,58 @@ class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Comp
MatteryScreen<DriveViewerMenu>(menu, inventory, title) { MatteryScreen<DriveViewerMenu>(menu, inventory, title) {
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> { override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
val frame = FramePanel(this, null, 0f, 0f, FRAME_WIDTH, FRAME_HEIGHT, getTitle()) val frame = FramePanel(this, null, 0f, 0f, 200f, 114f, getTitle())
frame.makeCloseButton() frame.makeCloseButton()
frame.onClose { onClose() } frame.onClose { onClose() }
val views = ArrayList<EditablePanel<*>>() val controls = DeviceControls(this, frame, redstoneConfig = menu.redstoneConfig, energyConfig = menu.energyConfig)
controls.sortingButtons(menu.settings::isAscending.asGetterSetter(), menu.settings::sorting.asGetterSetter(), ItemStorageStackSorter.DEFAULT) {
for (v in ItemStorageStackSorter.entries) {
add(v, v.icon, v.title)
}
}
val view = ArrayList<EditablePanel<*>>()
val settings = ArrayList<EditablePanel<*>>() val settings = ArrayList<EditablePanel<*>>()
frame.Tab(onOpen = { view.add(EditablePanel(this, frame, width = AbstractSlotPanel.SIZE).also {
for (panel in views) { it.dock = Dock.LEFT
panel.visible = true WideProfiledPowerGaugePanel(this, it, menu.profiledEnergy).also {
} it.dock = Dock.TOP
}, onClose = { it.dockBottom = 6f
for (panel in views) {
panel.visible = false
} }
BatterySlotPanel(this, it, menu.batterySlot).dock = Dock.TOP
SlotPanel(this, it, menu.driveSlot).dock = Dock.TOP
}) })
frame.Tab(onOpen = { view.add(NetworkedItemGridPanel(this, frame, menu.networkedItemView).also {
for (panel in settings) { it.dock = Dock.FILL
panel.visible = true it.setDockMargin(4f, 0f, 0f, 0f)
}
}, onClose = {
for (panel in settings) {
panel.visible = false
}
}) })
views.add(PowerGaugePanel(this, frame, menu.energyWidget, 8f, 16f)) val filterGrid = GridPanel(this, frame, width = AbstractSlotPanel.SIZE * 3f, height = AbstractSlotPanel.SIZE * 4f, rows = 3, columns = 4)
views.add(BatterySlotPanel(this, frame, menu.batterySlot, 8f, 67f)) filterGrid.dock = Dock.FILL
views.add(SlotPanel(this, frame, menu.driveSlot, 8f, 85f)) filterGrid.dockResize = DockResizeMode.NONE
filterGrid.dockTop = 20f
val grid = GridPanel(this, frame, 28f, 16f, GRID_WIDTH * 18f, GRID_HEIGHT * 18f, GRID_WIDTH, GRID_HEIGHT) settings.add(filterGrid)
val scrollBar = DiscreteScrollBarPanel(this, frame, { integerDivisionDown(menu.networkedItemView.itemCount, GRID_WIDTH) }, { _, _, _ -> }, 192f, 14f, 92f)
views.add(grid)
views.add(scrollBar)
for (i in 0 until GRID_WIDTH * GRID_HEIGHT) {
object : AbstractSlotPanel<DriveViewerScreen>(this@DriveViewerScreen, grid, 0f, 0f) {
override val itemStack: ItemStack get() {
val index = i + scrollBar.scroll * GRID_WIDTH
return menu.networkedItemView.sortedView.getOrNull(index)?.stack?.toItemStack() ?: ItemStack.EMPTY
}
override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean {
return scrollBar.mouseScrolledInner(x, y, scroll)
}
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
val index = i + scrollBar.scroll * GRID_WIDTH
menu.networkedItemView.mouseClick(index, button)
return true
}
override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
renderSlotBackground(graphics, mouseX, mouseY, partialTick)
renderRegular(graphics, itemStack, "")
}
override fun getItemStackTooltip(stack: ItemStack): List<Component> {
return super.getItemStackTooltip(stack).also {
it as MutableList<Component>
val index = i + scrollBar.scroll * GRID_WIDTH
val realStack = menu.networkedItemView.sortedView.getOrNull(index)!!.stack
it.add(TranslatableComponent("otm.gui.item_amount", realStack.count.toString()))
}
}
}
}
val dock_left = FlexGridPanel(this, frame, 0f, 0f, 90f, 0f)
dock_left.dock = Dock.LEFT
dock_left.setDockMargin(4f, 0f, 4f, 0f)
val grid_filter = FlexGridPanel(this, frame)
grid_filter.dock = Dock.FILL
settings.add(dock_left)
settings.add(grid_filter)
for (i in 0 until PortableCondensationDriveItem.MAX_FILTERS) { for (i in 0 until PortableCondensationDriveItem.MAX_FILTERS) {
FilterSlotPanel(this, grid_filter, menu.driveFilterSlots[i], 0f, 0f) FilterSlotPanel(this, filterGrid, menu.driveFilterSlots[i], 0f, 0f)
} }
CheckBoxLabelInputPanel(this, dock_left, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) } settings.add(EditablePanel(this, frame, width = 90f).also {
CheckBoxLabelInputPanel(this, dock_left, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) } it.dock = Dock.LEFT
CheckBoxLabelInputPanel(this, dock_left, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) } CheckBoxLabelInputPanel(this, it, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { it.dockTop = 20f; it.dock = Dock.TOP }
CheckBoxLabelInputPanel(this, it, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { it.dockTop = 4f; it.dock = Dock.TOP }
CheckBoxLabelInputPanel(this, it, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt")).also { it.dockTop = 4f; it.dock = Dock.TOP }
})
for (panel in settings) { frame.Tab(view, activeIcon = ItemStackIcon(ItemStack(MItems.PORTABLE_CONDENSATION_DRIVE)))
panel.visible = false frame.Tab(settings, activeIcon = ItemStackIcon(ItemStack(Items.HOPPER)))
}
return frame return frame
} }
companion object {
const val FRAME_WIDTH = 210f
const val FRAME_HEIGHT = 110f
const val GRID_WIDTH = 9
const val GRID_HEIGHT = 5
}
} }

View File

@ -1,23 +1,21 @@
package ru.dbotthepony.mc.otm.client.screen.storage package ru.dbotthepony.mc.otm.client.screen.storage
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items import net.minecraft.world.item.Items
import org.lwjgl.opengl.GL11
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.render.Widgets8
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen 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.Dock
import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode 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.EditablePanel
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
import ru.dbotthepony.mc.otm.client.screen.panels.NetworkedItemGridPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeBooleanRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeEnumRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.SmallEnumRectangleButtonPanel import ru.dbotthepony.mc.otm.client.screen.panels.button.SmallEnumRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel
@ -27,12 +25,10 @@ import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel 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.WidePowerGaugePanel
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.asGetterSetter import ru.dbotthepony.mc.otm.core.asGetterSetter
import ru.dbotthepony.mc.otm.core.math.integerDivisionDown import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.core.util.formatReadableNumber
import ru.dbotthepony.mc.otm.core.util.formatSiComponent
import ru.dbotthepony.mc.otm.menu.storage.ItemMonitorMenu import ru.dbotthepony.mc.otm.menu.storage.ItemMonitorMenu
import ru.dbotthepony.mc.otm.storage.StorageStack
import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak
@MouseTweaksDisableWheelTweak @MouseTweaksDisableWheelTweak
@ -57,71 +53,17 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
frame.height = topPanel.height + bottomPanel.height + frame.dockPadding.top + frame.dockPadding.bottom + 3f frame.height = topPanel.height + bottomPanel.height + frame.dockPadding.top + frame.dockPadding.bottom + 3f
frame.width = 178f + frame.dockPadding.left + frame.dockPadding.right frame.width = 178f + frame.dockPadding.left + frame.dockPadding.right
val viewScrollBar = DiscreteScrollBarPanel(this, topPanel, val controls = DeviceControls(this, frame)
{ integerDivisionDown(menu.networkedItemView.itemCount, ITEM_GRID_WIDTH) },
{ _, _, _ -> },
28f + ITEM_GRID_WIDTH * 18f + 2f, 16f, ITEM_GRID_HEIGHT * 18f)
viewScrollBar.dock = Dock.RIGHT controls.sortingButtons(menu.settings::ascendingSort.asGetterSetter(), menu.settings::sorting.asGetterSetter(), ItemStorageStackSorter.DEFAULT) {
viewScrollBar.setDockMargin(left = 2f) for (v in ItemStorageStackSorter.entries) {
add(v, skinElement = v.icon, tooltip = v.title)
val gridPanel = GridPanel(this, topPanel, width = ITEM_GRID_WIDTH * 18f, height = ITEM_GRID_HEIGHT * 18f, columns = ITEM_GRID_WIDTH, rows = ITEM_GRID_HEIGHT)
gridPanel.dock = Dock.FILL
for (i in 0 until ITEM_GRID_WIDTH * ITEM_GRID_HEIGHT) {
object : AbstractSlotPanel<ItemMonitorScreen>(this@ItemMonitorScreen, gridPanel) {
private val index get() = i + viewScrollBar.scroll * ITEM_GRID_WIDTH
override val itemStack: ItemStack get() {
return menu.networkedItemView.sortedView.getOrNull(index)?.stack?.toItemStack() ?: ItemStack.EMPTY
}
override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean {
return viewScrollBar.mouseScrolledInner(x, y, scroll)
}
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
menu.networkedItemView.mouseClick(index, button)
return true
}
override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
renderSlotBackground(graphics, mouseX, mouseY, partialTick)
val itemstack = menu.networkedItemView.sortedView.getOrNull(index)?.stack ?: StorageStack.ITEMS.empty
val stack = graphics.pose()
renderRegular(graphics, itemstack.toItemStack(), "")
if (!itemstack.isEmpty) {
stack.pushPose()
val text = itemstack.count.formatSiComponent(decimalPlaces = 1)
val textWidth = font.width(text)
stack.translate((width - textWidth * FONT_SCALE).toDouble(), (height - font.lineHeight * FONT_SCALE).toDouble(), 103.0)
stack.scale(FONT_SCALE, FONT_SCALE, 1f)
RenderSystem.depthFunc(GL11.GL_ALWAYS)
graphics.drawString(font, text, 1, 1, 0x0)
graphics.drawString(font, text, 0, 0, 16777215)
stack.popPose()
}
}
override fun getItemStackTooltip(stack: ItemStack): List<Component> {
return super.getItemStackTooltip(stack).also {
it as MutableList<Component>
val realStack = menu.networkedItemView.sortedView.getOrNull(index)!!.stack
it.add(TranslatableComponent("otm.gui.stored_amount", realStack.count.formatReadableNumber()).withStyle(ChatFormatting.DARK_GRAY))
}
}
} }
} }
val grid = NetworkedItemGridPanel(this, topPanel, menu.networkedItemView)
grid.dock = Dock.FILL
val craftingGrid = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3) val craftingGrid = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3)
craftingGrid.dock = Dock.LEFT craftingGrid.dock = Dock.LEFT
@ -142,19 +84,19 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
val arrowLine = EditablePanel(this, arrowAndButtons, y = 38f, height = 8f, width = arrowAndButtons.width) val arrowLine = EditablePanel(this, arrowAndButtons, y = 38f, height = 8f, width = arrowAndButtons.width)
val refillPriority = SmallEnumRectangleButtonPanel(this, arrowLine, SmallEnumRectangleButtonPanel(this, arrowLine,
enum = ItemMonitorPlayerSettings.IngredientPriority::class.java, enum = ItemMonitorPlayerSettings.IngredientPriority::class.java,
prop = menu.settings::ingredientPriority.asGetterSetter(watch = { _, _ -> menu.sendSettingsToServer() }), prop = menu.settings::ingredientPriority.asGetterSetter(),
defaultValue = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM) defaultValue = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM)
.also {
it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.refill_source.desc"))
refillPriority.tooltips.add(TranslatableComponent("otm.gui.item_monitor.refill_source.desc")) for (setting in ItemMonitorPlayerSettings.IngredientPriority.entries) {
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.SYSTEM, tooltip = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM.component, skinElement = Widgets8.WHITE_ARROW_DOWN, winding = UVWindingOrder.FLIP) it.add(setting, setting.icon, setting.component, setting.winding)
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.INVENTORY, tooltip = ItemMonitorPlayerSettings.IngredientPriority.INVENTORY.component, skinElement = Widgets8.WHITE_ARROW_DOWN) }
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST, tooltip = ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST.component, skinElement = Widgets8.ARROW_SIDEWAYS, winding = UVWindingOrder.FLIP)
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST, tooltip = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST.component, skinElement = Widgets8.ARROW_SIDEWAYS)
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.DO_NOT, tooltip = ItemMonitorPlayerSettings.IngredientPriority.DO_NOT.component, skinElement = Widgets8.MINUS)
refillPriority.dock = Dock.LEFT it.dock = Dock.LEFT
}
val resultAndButtons = EditablePanel(this, bottomPanel, width = 18f) val resultAndButtons = EditablePanel(this, bottomPanel, width = 18f)
@ -163,25 +105,29 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
SlotPanel(this, resultAndButtons, menu.craftingResult, y = 18f) SlotPanel(this, resultAndButtons, menu.craftingResult, y = 18f)
val resultTarget = SmallEnumRectangleButtonPanel(this, resultAndButtons, y = 38f, SmallEnumRectangleButtonPanel(this, resultAndButtons, y = 38f,
enum = ItemMonitorPlayerSettings.ResultTarget::class.java, enum = ItemMonitorPlayerSettings.ResultTarget::class.java,
prop = menu.settings::resultTarget.asGetterSetter(watch = { _, _ -> menu.sendSettingsToServer() }), prop = menu.settings::resultTarget.asGetterSetter(),
defaultValue = ItemMonitorPlayerSettings.ResultTarget.MIXED) defaultValue = ItemMonitorPlayerSettings.ResultTarget.MIXED)
.also {
it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.result_target.desc"))
resultTarget.tooltips.add(TranslatableComponent("otm.gui.item_monitor.result_target.desc")) for (setting in ItemMonitorPlayerSettings.ResultTarget.entries) {
resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.MIXED, tooltip = ItemMonitorPlayerSettings.ResultTarget.MIXED.component, skinElement = Widgets8.ARROW_SIDEWAYS) it.add(setting, setting.icon, setting.component, setting.winding)
resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY, tooltip = ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY.component, skinElement = Widgets8.ARROW_PAINTED_UP, winding = UVWindingOrder.FLIP) }
resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM, tooltip = ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM.component, skinElement = Widgets8.ARROW_PAINTED_UP) }
val craftingAmount = SmallEnumRectangleButtonPanel(this, resultAndButtons, x = 10f, y = 38f, SmallEnumRectangleButtonPanel(this, resultAndButtons, x = 10f, y = 38f,
enum = ItemMonitorPlayerSettings.Amount::class.java, enum = ItemMonitorPlayerSettings.Amount::class.java,
prop = menu.settings::craftingAmount.asGetterSetter(watch = { _, _ -> menu.sendSettingsToServer() }), prop = menu.settings::craftingAmount.asGetterSetter(),
defaultValue = ItemMonitorPlayerSettings.Amount.STACK) defaultValue = ItemMonitorPlayerSettings.Amount.STACK)
.also {
it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.amount.desc"))
craftingAmount.tooltips.add(TranslatableComponent("otm.gui.item_monitor.amount.desc")) for (setting in ItemMonitorPlayerSettings.Amount.entries) {
craftingAmount.add(ItemMonitorPlayerSettings.Amount.ONE, tooltip = ItemMonitorPlayerSettings.Amount.ONE.component, skinElement = Widgets8.ONE) it.add(setting, setting.icon, setting.component, setting.winding)
craftingAmount.add(ItemMonitorPlayerSettings.Amount.STACK, tooltip = ItemMonitorPlayerSettings.Amount.STACK.component, skinElement = Widgets8.S) }
craftingAmount.add(ItemMonitorPlayerSettings.Amount.FULL, tooltip = ItemMonitorPlayerSettings.Amount.FULL.component, skinElement = Widgets8.F) }
val craftingHistory = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3) val craftingHistory = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3)
craftingHistory.dock = Dock.LEFT craftingHistory.dock = Dock.LEFT
@ -227,7 +173,5 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
companion object { companion object {
const val ITEM_GRID_WIDTH = 9 const val ITEM_GRID_WIDTH = 9
const val ITEM_GRID_HEIGHT = 5 const val ITEM_GRID_HEIGHT = 5
const val FONT_SCALE = 0.6f
} }
} }

View File

@ -139,10 +139,10 @@ object MachinesConfig : AbstractConfig("machines") {
} }
val STORAGE_POWER_SUPPLIER = conciseValues(MNames.STORAGE_POWER_SUPPLIER, Decimal(80_000), Decimal(2_000)) val STORAGE_POWER_SUPPLIER = conciseValues(MNames.STORAGE_POWER_SUPPLIER, Decimal(80_000), Decimal(2_000))
val STORAGE_INTERFACES = conciseValues("STORAGE_INTERFACES", Decimal(10_000), Decimal(100)) val STORAGE_INTERFACES = conciseValues("STORAGE_INTERFACES", Decimal(10_000), Decimal(10_000))
val ITEM_MONITOR = conciseValues(MNames.ITEM_MONITOR, Decimal(10_000), Decimal(100)) val ITEM_MONITOR = conciseValues(MNames.ITEM_MONITOR, Decimal(10_000), Decimal(10_000))
val DRIVE_VIEWER = conciseValues(MNames.DRIVE_VIEWER, Decimal(10_000), Decimal(100)) val DRIVE_VIEWER = conciseValues(MNames.DRIVE_VIEWER, Decimal(10_000), Decimal(10_000))
val DRIVE_RACK = conciseValues(MNames.DRIVE_RACK, Decimal(10_000), Decimal(100)) val DRIVE_RACK = conciseValues(MNames.DRIVE_RACK, Decimal(10_000), Decimal(10_000))
object AndroidCharger { object AndroidCharger {
val RADIUS_WIDTH: Double by builder val RADIUS_WIDTH: Double by builder

View File

@ -12,6 +12,7 @@ import net.minecraft.world.Container
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.collect.concatIterators
import ru.dbotthepony.mc.otm.core.collect.filter import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.flatMap import ru.dbotthepony.mc.otm.core.collect.flatMap
import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.collect.map
@ -167,10 +168,10 @@ class CombinedContainer(containers: Stream<Pair<Container, Iterator<Int>>>) : Co
} }
fun optimizedIterator(): Iterator<ItemStack> { fun optimizedIterator(): Iterator<ItemStack> {
return listOf( return concatIterators(
fullCoverage.iterator().flatMap { it.iterator() }, fullCoverage.iterator().flatMap { it.iterator() },
notFullCoverage.values.iterator().flatMap { it.iterator() }.map { it.item }.filter { it.isNotEmpty } notFullCoverage.values.iterator().flatMap { it.iterator() }.map { it.item }.filter { it.isNotEmpty }
).iterator().flatMap { it } )
} }
class Builder { class Builder {

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.container package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.objects.ObjectIterators
import net.minecraft.world.Container import net.minecraft.world.Container
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.isNotEmpty import ru.dbotthepony.mc.otm.core.isNotEmpty
@ -50,3 +51,32 @@ fun Container.iterator(): IContainerIterator {
ContainerIterator(this) ContainerIterator(this)
} }
} }
class FullContainerIterator(val container: Container, initialPos: Int = 0) : MutableIterator<ItemStack>, ObjectIterators.AbstractIndexBasedListIterator<ItemStack>(0, initialPos), IContainerIterator {
override fun remove(location: Int) {
pos++
container[location] = ItemStack.EMPTY
}
override fun get(location: Int): ItemStack {
return container[location]
}
override fun getMaxPos(): Int {
return container.containerSize
}
override fun add(location: Int, k: ItemStack?) {
throw UnsupportedOperationException()
}
override fun set(location: Int, k: ItemStack) {
container[location] = k
}
override fun setChanged() {
container.setChanged()
}
}
fun Container.fullIterator() = FullContainerIterator(this)

View File

@ -53,6 +53,7 @@ import java.util.Arrays
import java.util.Spliterators import java.util.Spliterators
import java.util.UUID import java.util.UUID
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Supplier
import java.util.stream.Stream import java.util.stream.Stream
import java.util.stream.StreamSupport import java.util.stream.StreamSupport
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@ -367,12 +368,20 @@ fun <T> Comparator<in T>.nullsLast(): Comparator<T?> {
return Comparator.nullsLast(this as Comparator<in T?>) return Comparator.nullsLast(this as Comparator<in T?>)
} }
fun <A, B> Comparator<A>.map(mapper: (B) -> A): Comparator<B> {
return Comparator { a, b -> this@map.compare(mapper.invoke(a), mapper.invoke(b)) }
}
fun <T> Comparator<T>.suppliers(): Comparator<Supplier<T>> {
return Comparator { o1, o2 -> this@suppliers.compare(o1.get(), o2.get()) }
}
/** /**
* Returns applicable index to put [element] into [List] determined by [comparator], optionally specifying ranges as [fromIndex] and [toIndex] * 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 * If [List] is not sorted, result of this function is undefined
*/ */
fun <E> List<E>.searchInsertionIndex(element: E, comparator: Comparator<E>, fromIndex: Int = 0, toIndex: Int = size): Int { fun <E> List<E>.searchInsertionIndex(element: E, comparator: Comparator<in E>, fromIndex: Int = 0, toIndex: Int = size): Int {
require(toIndex >= fromIndex) { "Invalid range: to $toIndex >= from $fromIndex" } require(toIndex >= fromIndex) { "Invalid range: to $toIndex >= from $fromIndex" }
require(fromIndex >= 0) { "Invalid from index: $fromIndex" } require(fromIndex >= 0) { "Invalid from index: $fromIndex" }
require(toIndex >= 0) { "Invalid to index: $toIndex" } require(toIndex >= 0) { "Invalid to index: $toIndex" }
@ -421,7 +430,7 @@ fun <E> List<E>.searchInsertionIndex(element: E, comparator: Comparator<E>, from
* *
* If [MutableList] is not sorted, result of this function is undefined * If [MutableList] is not sorted, result of this function is undefined
*/ */
fun <E> MutableList<E>.addSorted(element: E, comparator: Comparator<E>) { fun <E> MutableList<E>.addSorted(element: E, comparator: Comparator<in E>) {
add(searchInsertionIndex(element, comparator), element) add(searchInsertionIndex(element, comparator), element)
} }
@ -433,3 +442,8 @@ fun <E> MutableList<E>.addSorted(element: E, comparator: Comparator<E>) {
fun <E : Comparable<E>> MutableList<E>.addSorted(element: E) { fun <E : Comparable<E>> MutableList<E>.addSorted(element: E) {
add(searchInsertionIndex(element, ObjectComparators.NATURAL_COMPARATOR), element) add(searchInsertionIndex(element, ObjectComparators.NATURAL_COMPARATOR), element)
} }
fun <A, B> lazy2(a: () -> A, b: A.() -> B): Supplier<B> {
val first = lazy(a)
return Supplier { b.invoke(first.value) }
}

View File

@ -93,8 +93,14 @@ interface GetterSetter<V> : Supplier<V>, Consumer<V>, ReadWriteProperty<Any?, V>
} }
} }
fun <V> box(value: V): GetterSetter<V> { fun <V> box(value: V): SentientGetterSetter<V> {
return object : GetterSetter<V> { return object : SentientGetterSetter<V> {
private val subs = ISubscriptable.Impl<V>()
override fun addListener(listener: Consumer<V>): ISubscriptable.L {
return subs.addListener(listener)
}
private var value = value private var value = value
override fun get(): V { override fun get(): V {
@ -103,12 +109,15 @@ interface GetterSetter<V> : Supplier<V>, Consumer<V>, ReadWriteProperty<Any?, V>
override fun accept(t: V) { override fun accept(t: V) {
this.value = t this.value = t
subs.accept(t)
} }
} }
} }
} }
} }
interface SentientGetterSetter<V> : GetterSetter<V>, ISubscriptable<V>
operator fun <T> Supplier<T>.getValue(thisRef: Any?, property: KProperty<*>): T { operator fun <T> Supplier<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
return get() return get()
} }

View File

@ -0,0 +1,213 @@
package ru.dbotthepony.mc.otm.core
import it.unimi.dsi.fastutil.booleans.BooleanConsumer
import it.unimi.dsi.fastutil.floats.FloatConsumer
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet
import java.util.function.Consumer
import java.util.function.DoubleConsumer
import java.util.function.IntConsumer
import java.util.function.LongConsumer
interface ISubscriptable<V> {
/**
* Listener token, allows to remove listener from subscriber list
*/
fun interface L {
/**
* Removes this listener
*/
fun remove()
}
fun addListener(listener: Consumer<V>): L
class Impl<V> : ISubscriptable<V>, Consumer<V> {
private inner class L(val callback: Consumer<V>) : ISubscriptable.L {
init {
subscribers.add(this)
}
override fun remove() {
subscribers.remove(this)
}
}
private val subscribers = ReferenceLinkedOpenHashSet<L>(0)
override fun addListener(listener: Consumer<V>): ISubscriptable.L {
return L(listener)
}
override fun accept(t: V) {
subscribers.forEach { it.callback.accept(t) }
}
}
companion object : ISubscriptable<Nothing>, L {
@Suppress("unchecked_cast")
fun <T> empty(): ISubscriptable<T> {
return this as ISubscriptable<T>
}
override fun remove() {}
override fun addListener(listener: Consumer<Nothing>): L {
return this
}
}
}
interface IFloatSubcripable : ISubscriptable<Float> {
@Deprecated("Use type specific listener")
override fun addListener(listener: Consumer<Float>): ISubscriptable.L {
return addListener(listener::accept)
}
fun addListener(listener: FloatConsumer): ISubscriptable.L
class Impl : IFloatSubcripable, Consumer<Float>, FloatConsumer {
private inner class L(val callback: FloatConsumer) : ISubscriptable.L {
init {
subscribers.add(this)
}
override fun remove() {
subscribers.remove(this)
}
}
private val subscribers = ReferenceLinkedOpenHashSet<L>(0)
override fun addListener(listener: FloatConsumer): ISubscriptable.L {
return L(listener)
}
override fun accept(t: Float) {
subscribers.forEach { it.callback.accept(t) }
}
}
}
interface IDoubleSubcripable : ISubscriptable<Double> {
@Deprecated("Use type specific listener")
override fun addListener(listener: Consumer<Double>): ISubscriptable.L {
return addListener(DoubleConsumer { listener.accept(it) })
}
fun addListener(listener: DoubleConsumer): ISubscriptable.L
class Impl : IDoubleSubcripable, Consumer<Double>, DoubleConsumer {
private inner class L(val callback: DoubleConsumer) : ISubscriptable.L {
init {
subscribers.add(this)
}
override fun remove() {
subscribers.remove(this)
}
}
private val subscribers = ReferenceLinkedOpenHashSet<L>(0)
override fun addListener(listener: DoubleConsumer): ISubscriptable.L {
return L(listener)
}
override fun accept(t: Double) {
subscribers.forEach { it.callback.accept(t) }
}
}
}
interface IIntSubcripable : ISubscriptable<Int> {
@Deprecated("Use type specific listener")
override fun addListener(listener: Consumer<Int>): ISubscriptable.L {
return addListener(IntConsumer { listener.accept(it) })
}
fun addListener(listener: IntConsumer): ISubscriptable.L
class Impl : IIntSubcripable, Consumer<Int>, IntConsumer {
private inner class L(val callback: IntConsumer) : ISubscriptable.L {
init {
subscribers.add(this)
}
override fun remove() {
subscribers.remove(this)
}
}
private val subscribers = ReferenceLinkedOpenHashSet<L>(0)
override fun addListener(listener: IntConsumer): ISubscriptable.L {
return L(listener)
}
override fun accept(t: Int) {
subscribers.forEach { it.callback.accept(t) }
}
}
}
interface ILongSubcripable : ISubscriptable<Long> {
@Deprecated("Use type specific listener")
override fun addListener(listener: Consumer<Long>): ISubscriptable.L {
return addListener(LongConsumer { listener.accept(it) })
}
fun addListener(listener: LongConsumer): ISubscriptable.L
class Impl : ILongSubcripable, Consumer<Long>, LongConsumer {
private inner class L(val callback: LongConsumer) : ISubscriptable.L {
init {
subscribers.add(this)
}
override fun remove() {
subscribers.remove(this)
}
}
private val subscribers = ReferenceLinkedOpenHashSet<L>(0)
override fun addListener(listener: LongConsumer): ISubscriptable.L {
return L(listener)
}
override fun accept(t: Long) {
subscribers.forEach { it.callback.accept(t) }
}
}
}
interface IBooleanSubscriptable : ISubscriptable<Boolean> {
@Deprecated("Use type specific listener")
override fun addListener(listener: Consumer<Boolean>): ISubscriptable.L {
return addListener(listener::accept)
}
fun addListener(listener: BooleanConsumer): ISubscriptable.L
class Impl : IBooleanSubscriptable, Consumer<Boolean>, BooleanConsumer {
private inner class L(val callback: BooleanConsumer) : ISubscriptable.L {
init {
subscribers.add(this)
}
override fun remove() {
subscribers.remove(this)
}
}
private val subscribers = ReferenceLinkedOpenHashSet<L>(0)
override fun addListener(listener: BooleanConsumer): ISubscriptable.L {
return L(listener)
}
override fun accept(t: Boolean) {
subscribers.forEach { it.callback.accept(t) }
}
}
}

View File

@ -251,7 +251,7 @@ fun <T> Iterator<T>.mapToDouble(mapper: O2DFunction<T>): it.unimi.dsi.fastutil.d
} }
override fun remove() { override fun remove() {
throw UnsupportedOperationException() (this@mapToDouble as MutableIterator).remove()
} }
override fun nextDouble(): Double { override fun nextDouble(): Double {
@ -267,7 +267,7 @@ fun <T> Iterator<T>.mapToInt(mapper: O2IFunction<T>): it.unimi.dsi.fastutil.ints
} }
override fun remove() { override fun remove() {
throw UnsupportedOperationException() (this@mapToInt as MutableIterator).remove()
} }
override fun nextInt(): Int { override fun nextInt(): Int {
@ -362,7 +362,7 @@ fun <T, A, R> Iterator<T>.collect(collector: Collector<T, A, R>): R {
return collector.finisher().apply(instance) return collector.finisher().apply(instance)
} }
fun <T> Iterator<T>.toList(): List<T> { fun <T> Iterator<T>.toList(): MutableList<T> {
val result = ArrayList<T>() val result = ArrayList<T>()
result.addAll(this) result.addAll(this)
return result return result

View File

@ -71,7 +71,12 @@ inline fun <R, reified T : Tag> CompoundTag.mapPresent(key: String, consumer: (T
fun <T> CompoundTag.mapString(index: String, mapper: (String) -> T, orElse: T): T { fun <T> CompoundTag.mapString(index: String, mapper: (String) -> T, orElse: T): T {
val tag = this[index] as? StringTag ?: return orElse val tag = this[index] as? StringTag ?: return orElse
return mapper.invoke(tag.asString)
return try {
mapper.invoke(tag.asString)
} catch (err: NoSuchElementException) {
orElse
}
} }
fun CompoundTag.getItemStack(key: String): ItemStack = map(key, ItemStack::of) ?: ItemStack.EMPTY fun CompoundTag.getItemStack(key: String): ItemStack = map(key, ItemStack::of) ?: ItemStack.EMPTY

View File

@ -96,7 +96,10 @@ fun BigInteger.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, forma
buffer[add + i + divided.length + 1] = prefix.paddedIndex(remainder, i) buffer[add + i + divided.length + 1] = prefix.paddedIndex(remainder, i)
} }
return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix) if (suffix == "")
return TranslatableComponent(prefix.conciseFormatLocaleKey, String(buffer))
else
return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix)
} }
private val never = BooleanSupplier { false } private val never = BooleanSupplier { false }

View File

@ -3,17 +3,31 @@ package ru.dbotthepony.mc.otm.core.util
import it.unimi.dsi.fastutil.objects.Reference2IntFunction import it.unimi.dsi.fastutil.objects.Reference2IntFunction
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.CreativeModeTabs import net.minecraft.world.item.CreativeModeTabs
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.CreativeModeTabRegistry import net.minecraftforge.common.CreativeModeTabRegistry
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.nullsFirst import ru.dbotthepony.mc.otm.core.nullsFirst
import ru.dbotthepony.mc.otm.core.nullsLast import ru.dbotthepony.mc.otm.core.nullsLast
import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.suppliers
import ru.dbotthepony.mc.otm.matter.MatterManager import ru.dbotthepony.mc.otm.matter.MatterManager
import ru.dbotthepony.mc.otm.storage.ItemStorageStack
import ru.dbotthepony.mc.otm.client.render.Widgets18
object CreativeMenuComparator : Comparator<Item> { private fun Comparator<Item?>.stacks(): Comparator<ItemStack?> {
return Comparator<ItemStack> { o1, o2 -> this@stacks.compare(o1.item, o2.item) }.nullsFirst()
}
private fun Comparator<Item?>.storage(): Comparator<ItemStorageStack?> {
return Comparator<ItemStorageStack> { o1, o2 -> this@storage.compare(o1.item, o2.item) }.nullsFirst()
}
object CreativeMenuItemComparator : Comparator<Item> {
override fun compare(o1: Item, o2: Item): Int { override fun compare(o1: Item, o2: Item): Int {
rebuild() rebuild()
return item2index.getInt(o1).compareTo(item2index.getInt(o2)) return item2index.getInt(o1).compareTo(item2index.getInt(o2))
@ -84,6 +98,24 @@ object ItemLocalizedNameComparator : Comparator<Item> {
val NullsLast = nullsLast() val NullsLast = nullsLast()
} }
object ItemStackNameComparator : Comparator<ItemStack> {
override fun compare(o1: ItemStack, o2: ItemStack): Int {
return o1.hoverName.string.compareTo(o2.hoverName.string)
}
val NullsFirst = nullsFirst()
val NullsLast = nullsLast()
}
object ItemStorageStackNameComparator : Comparator<ItemStorageStack> {
override fun compare(o1: ItemStorageStack, o2: ItemStorageStack): Int {
return o1.hoverName.string.compareTo(o2.hoverName.string)
}
val NullsFirst = nullsFirst()
val NullsLast = nullsLast()
}
object ItemModComparator : Comparator<Item> { object ItemModComparator : Comparator<Item> {
override fun compare(o1: Item, o2: Item): Int { override fun compare(o1: Item, o2: Item): Int {
val a = o1.registryName?.namespace ?: return 0 val a = o1.registryName?.namespace ?: return 0
@ -106,14 +138,65 @@ object ItemIDComparator : Comparator<Item> {
val NullsLast = nullsLast() val NullsLast = nullsLast()
} }
enum class ItemSorter(val comparator: Comparator<Item?>, private val sTitle: Component) { object ItemStackCountComparator : Comparator<ItemStack> {
DEFAULT(CreativeMenuComparator.NullsFirst, TranslatableComponent("otm.gui.sorting.default")), override fun compare(o1: ItemStack, o2: ItemStack): Int {
NAME(ItemLocalizedNameComparator.NullsFirst.thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.name")), return o1.count.compareTo(o2.count)
ID(ItemIDComparator.NullsFirst.thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.id")), }
MOD(ItemModComparator.NullsFirst.thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.modid")),
MATTER_VALUE(MatterValueComparator.NullsFirst.thenComparing(MatterComplexityComparator.NullsFirst).thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.matter_value")), val NullsFirst = nullsFirst()
MATTER_COMPLEXITY(MatterComplexityComparator.NullsFirst.thenComparing(MatterValueComparator.NullsFirst).thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.matter_complexity")), val NullsLast = nullsLast()
}
object ItemStorageStackCountComparator : Comparator<ItemStorageStack> {
override fun compare(o1: ItemStorageStack, o2: ItemStorageStack): Int {
return o1.count.compareTo(o2.count)
}
val NullsFirst = nullsFirst()
val NullsLast = nullsLast()
}
enum class ItemSorter(comparator: Comparator<Item>, private val sTitle: Component, icon: Lazy<IGUIRenderable>) : Comparator<Item?> by comparator.nullsFirst() {
DEFAULT(CreativeMenuItemComparator, TranslatableComponent("otm.gui.sorting.default"), lazy { Widgets18.SORT_DEFAULT }),
NAME(ItemLocalizedNameComparator.thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.name"), lazy { Widgets18.SORT_ALPHABET }),
ID(ItemIDComparator.thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.id"), lazy { Widgets18.SORT_ID }),
MOD(ItemModComparator.thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.modid"), lazy { Widgets18.SORT_MODID }),
MATTER_VALUE(MatterValueComparator.thenComparing(MatterComplexityComparator).thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.matter_value"), lazy { Widgets18.SORT_MATTER_VALUE }),
MATTER_COMPLEXITY(MatterComplexityComparator.thenComparing(MatterValueComparator).thenComparing(CreativeMenuItemComparator), TranslatableComponent("otm.gui.sorting.matter_complexity"), lazy { Widgets18.SORT_MATTER_COMPLEXITY }),
; ;
val title: Component get() = sTitle.copy() val icon: IGUIRenderable by icon
val title: MutableComponent get() = sTitle.copy()
val suppliers = suppliers()
val reversed: Comparator<Item?> = reversed()
}
enum class ItemStackSorter(comparator: Comparator<ItemStack?>, private val sTitle: Component, icon: Lazy<IGUIRenderable>) : Comparator<ItemStack?> by comparator {
DEFAULT(ItemSorter.DEFAULT.stacks(), ItemSorter.DEFAULT.title, lazy { Widgets18.SORT_DEFAULT }),
COUNT(ItemStackCountComparator.NullsFirst.thenComparing(ItemSorter.DEFAULT.stacks()), TranslatableComponent("otm.gui.sorting.count"), lazy { Widgets18.SORT_COUNT }),
NAME(ItemStackNameComparator.NullsFirst.thenComparing(ItemSorter.DEFAULT.stacks()), ItemSorter.NAME.title, lazy { Widgets18.SORT_ALPHABET }),
ID(ItemSorter.ID.stacks(), ItemSorter.ID.title, lazy { Widgets18.SORT_ID }),
MOD(ItemSorter.MOD.stacks(), ItemSorter.MOD.title, lazy { Widgets18.SORT_MODID }),
MATTER_VALUE(ItemSorter.MATTER_VALUE.stacks(), ItemSorter.MATTER_VALUE.title, lazy { Widgets18.SORT_MATTER_VALUE }),
MATTER_COMPLEXITY(ItemSorter.MATTER_COMPLEXITY.stacks(), ItemSorter.MATTER_COMPLEXITY.title, lazy { Widgets18.SORT_MATTER_COMPLEXITY });
val icon: IGUIRenderable by icon
val title: MutableComponent get() = sTitle.copy()
val suppliers = suppliers()
val reversed: Comparator<ItemStack?> = reversed()
}
enum class ItemStorageStackSorter(comparator: Comparator<ItemStorageStack?>, private val sTitle: Component, icon: Lazy<IGUIRenderable>) : Comparator<ItemStorageStack?> by comparator {
DEFAULT(ItemSorter.DEFAULT.storage(), ItemSorter.DEFAULT.title, lazy { Widgets18.SORT_DEFAULT }),
COUNT(ItemStorageStackCountComparator.NullsFirst.thenComparing(ItemSorter.DEFAULT.storage()), TranslatableComponent("otm.gui.sorting.count"), lazy { Widgets18.SORT_COUNT }),
NAME(ItemStorageStackNameComparator.NullsFirst.thenComparing(ItemSorter.DEFAULT.storage()), ItemSorter.NAME.title, lazy { Widgets18.SORT_ALPHABET }),
ID(ItemSorter.ID.storage(), ItemSorter.ID.title, lazy { Widgets18.SORT_ID }),
MOD(ItemSorter.MOD.storage(), ItemSorter.MOD.title, lazy { Widgets18.SORT_MODID }),
MATTER_VALUE(ItemSorter.MATTER_VALUE.storage(), ItemSorter.MATTER_VALUE.title, lazy { Widgets18.SORT_MATTER_VALUE }),
MATTER_COMPLEXITY(ItemSorter.MATTER_COMPLEXITY.storage(), ItemSorter.MATTER_COMPLEXITY.title, lazy { Widgets18.SORT_MATTER_COMPLEXITY });
val icon: IGUIRenderable by icon
val title: MutableComponent get() = sTitle.copy()
val suppliers = suppliers()
val reversed: Comparator<ItemStorageStack?> = reversed()
} }

View File

@ -33,6 +33,7 @@ enum class SiPrefix(val power: Int, val symbol: String) {
open val isEmpty: Boolean get() = false open val isEmpty: Boolean get() = false
val formatLocaleKey = "otm.suffix.${name.lowercase()}".intern() val formatLocaleKey = "otm.suffix.${name.lowercase()}".intern()
val conciseFormatLocaleKey = "otm.suffix_concise.${name.lowercase()}".intern()
val rawLocaleKey = "otm.suffix_raw.${name.lowercase()}".intern() val rawLocaleKey = "otm.suffix_raw.${name.lowercase()}".intern()
val string: String val string: String

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.core.util package ru.dbotthepony.mc.otm.core.util
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.core.addSorted import ru.dbotthepony.mc.otm.core.addSorted
@ -15,6 +16,7 @@ class TickList : ITickable {
private val toRemoveFromAlways = ArrayList<ITickable>() private val toRemoveFromAlways = ArrayList<ITickable>()
private val timers = ArrayDeque<Timer>() private val timers = ArrayDeque<Timer>()
private val namedTimers = Object2ObjectOpenHashMap<Any, Timer>(0)
var inTicker = false var inTicker = false
private set private set
@ -42,6 +44,20 @@ class TickList : ITickable {
} }
} }
/**
* Calling this method while timer already exists removes old timer
*/
fun namedTimer(name: Any?, ticks: Int, callback: Runnable): Timer {
val remove = namedTimers.remove(name)
if (remove != null)
timers.remove(remove)
val timer = Timer(ticks, callback)
namedTimers[name] = timer
return timer
}
inner class Ticker(parent: ITickable) : ITickable by parent { inner class Ticker(parent: ITickable) : ITickable by parent {
init { init {
add(this, always, alwaysQueued) add(this, always, alwaysQueued)

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.graph.storage package ru.dbotthepony.mc.otm.graph.storage
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
@ -13,7 +14,7 @@ import java.util.*
import java.util.function.Supplier import java.util.function.Supplier
open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null) : GraphNode<StorageNode, StorageGraph>(::StorageGraph) { open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null) : GraphNode<StorageNode, StorageGraph>(::StorageGraph) {
protected val components = ArrayList<IStorage<*>>() protected val components = ObjectArraySet<IStorage<*>>()
/** /**
* Setting this to true will render non functional default [attachComponents], * Setting this to true will render non functional default [attachComponents],
@ -74,32 +75,15 @@ open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null
} }
} }
@Suppress("unchecked_cast")
fun <T : StorageStack<T>, U : IStorage<T>> computeIfAbsent(type: StorageStack.Type<T>, provider: (StorageStack.Type<T>) -> U): U {
return components.firstOrNull { it.storageType == type } as U? ?: provider(type).also { addStorageComponent(it) }
}
fun addStorageComponent(component: IStorage<*>) { fun addStorageComponent(component: IStorage<*>) {
if (!components.any { component === it || it.storageType === component.storageType }) { if (components.add(component))
components.add(component)
if (isValid && !manualAttaching) if (isValid && !manualAttaching)
graph.add(component) graph.add(component)
}
} }
fun removeStorageComponent(component: IStorage<*>) { fun removeStorageComponent(component: IStorage<*>) {
val indexOf = components.indexOfFirst { component === it || it.storageType === component.storageType } if (components.remove(component) && isValid)
if (indexOf == -1) return graph.remove(component)
val self = components.removeAt(indexOf)
if (isValid) graph.remove(self)
}
fun removeStorageComponent(component: StorageStack.Type<*>) {
val indexOf = components.indexOfFirst { it.storageType === component }
if (indexOf == -1) return
val self = components.removeAt(indexOf)
if (isValid) graph.remove(self)
} }
override fun invalidate() { override fun invalidate() {

View File

@ -54,6 +54,7 @@ import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.network.MenuFieldPacket import ru.dbotthepony.mc.otm.network.MenuFieldPacket
import ru.dbotthepony.mc.otm.network.MenuNetworkChannel import ru.dbotthepony.mc.otm.network.MenuNetworkChannel
import ru.dbotthepony.mc.otm.network.SetCarriedPacket
import ru.dbotthepony.mc.otm.network.enqueueWork import ru.dbotthepony.mc.otm.network.enqueueWork
import ru.dbotthepony.mc.otm.network.packetHandled import ru.dbotthepony.mc.otm.network.packetHandled
import ru.dbotthepony.mc.otm.network.sender import ru.dbotthepony.mc.otm.network.sender
@ -461,6 +462,16 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return player.distanceToSqr(pos.x.toDouble() + 0.5, pos.y.toDouble() + 0.5, pos.z.toDouble() + 0.5) <= 64.0 return player.distanceToSqr(pos.x.toDouble() + 0.5, pos.y.toDouble() + 0.5, pos.z.toDouble() + 0.5) <= 64.0
} }
fun syncCarried() {
setRemoteCarried(carried.copy())
MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(carried))
}
fun syncCarried(stack: ItemStack) {
carried = stack
syncCarried()
}
private val externalSlots = ConditionalSet<Slot>() private val externalSlots = ConditionalSet<Slot>()
private val quickMoveMapping = Reference2ObjectOpenHashMap<Slot, ReferenceArrayList<Collection<Slot>>>() private val quickMoveMapping = Reference2ObjectOpenHashMap<Slot, ReferenceArrayList<Collection<Slot>>>()
@ -655,9 +666,9 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
// first pass - stack with existing slots // first pass - stack with existing slots
if (copy.isStackable) { if (copy.isStackable) {
for (slot in slots) { for (slot in slots) {
if (onlyFiltered && (slot !is UserFilteredSlot || slot.filter == null || slot.filter!!.get() != item.item)) { if (onlyFiltered && (slot !is UserFilteredSlot || !slot.test(item))) {
continue continue
} else if (!onlyFiltered && slot is UserFilteredSlot && slot.filter != null && slot.filter!!.get() != null && slot.filter!!.get() != item.item) { } else if (!onlyFiltered && slot is UserFilteredSlot && !slot.test(item)) {
continue continue
} }

View File

@ -15,6 +15,7 @@ import ru.dbotthepony.mc.otm.container.UpgradeContainer
import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.runOnClient import ru.dbotthepony.mc.otm.runOnClient
import java.util.function.Predicate
/** /**
* Make slots for single container * Make slots for single container
@ -46,7 +47,7 @@ open class MatterySlot(container: Container, index: Int, x: Int = 0, y: Int = 0)
} }
} }
open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y) { open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int = 0) : MatterySlot(container, index, x, y), Predicate<ItemStack> {
var hasSetFilter = false var hasSetFilter = false
private set private set
@ -60,6 +61,10 @@ open class UserFilteredSlot(container: Container, index: Int, x: Int = 0, y: Int
return filter?.get() == null return filter?.get() == null
} }
override fun test(t: ItemStack): Boolean {
return filter?.get() == null || filter?.get() == t.item
}
fun isSameFilter(other: Slot): Boolean { fun isSameFilter(other: Slot): Boolean {
if (other !is UserFilteredSlot) if (other !is UserFilteredSlot)
return filter?.get() == null return filter?.get() == null

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.menu.data
import com.mojang.blaze3d.platform.InputConstants import com.mojang.blaze3d.platform.InputConstants
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.client.gui.screens.Screen import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.FriendlyByteBuf
@ -13,21 +14,28 @@ import net.minecraft.world.item.ItemStack
import net.minecraftforge.network.NetworkEvent import net.minecraftforge.network.NetworkEvent
import net.minecraftforge.network.PacketDistributor import net.minecraftforge.network.PacketDistributor
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.addSorted
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.map
import ru.dbotthepony.mc.otm.core.readBigInteger import ru.dbotthepony.mc.otm.core.readBigInteger
import ru.dbotthepony.mc.otm.core.writeBigInteger import ru.dbotthepony.mc.otm.core.writeBigInteger
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.network.* import ru.dbotthepony.mc.otm.network.*
import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.storage.* import ru.dbotthepony.mc.otm.storage.*
import java.math.BigInteger import java.math.BigInteger
import java.util.* import java.util.*
import java.util.function.Supplier import java.util.function.Supplier
import kotlin.collections.ArrayList
interface INetworkedItemViewProvider { interface INetworkedItemViewProvider {
val networkedItemView: NetworkedItemView val networkedItemView: NetworkedItemView
} }
class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: ClickAction) : MatteryPacket { private fun all(stack: ItemStack): Int = stack.maxStackSize.coerceAtMost(stack.count)
private fun half(stack: ItemStack): Int = (stack.maxStackSize.coerceAtMost(stack.count) / 2).coerceAtLeast(1)
data class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action: ClickAction) : MatteryPacket {
override fun write(buff: FriendlyByteBuf) { override fun write(buff: FriendlyByteBuf) {
buff.writeInt(stackID) buff.writeInt(stackID)
buff.writeEnum(type) buff.writeEnum(type)
@ -53,144 +61,88 @@ class ItemViewInteractPacket(val stackID: Int, val type: ClickType, val action:
} }
} }
object ClearItemViewPacket : MatteryPacket { abstract class NetworkedItemViewPacket : MatteryPacket {
override fun write(buff: FriendlyByteBuf) { final override fun play(context: Supplier<NetworkEvent.Context>) {
// NO-OP
}
override fun play(context: Supplier<NetworkEvent.Context>) {
context.packetHandled = true context.packetHandled = true
context.enqueueWork { context.enqueueWork {
(minecraft.player?.containerMenu as? INetworkedItemViewProvider)?.networkedItemView?.clear() val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork
val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No NetworkedItemView is present in currently open menu")
action(view)
} }
} }
fun read(buff: FriendlyByteBuf): ClearItemViewPacket { protected abstract fun action(view: NetworkedItemView)
return ClearItemViewPacket }
}
fun send(ply: ServerPlayer) { object ClearItemViewPacket : NetworkedItemViewPacket() {
MenuNetworkChannel.send(ply, this) override fun write(buff: FriendlyByteBuf) {}
override fun action(view: NetworkedItemView) {
view.clear()
} }
} }
class StackAddPacket(val containerId: Int, val id: Int, val stack: ItemStorageStack) : MatteryPacket { class StackAddPacket(val stackId: Int, val stack: ItemStorageStack) : NetworkedItemViewPacket() {
override fun write(buff: FriendlyByteBuf) { override fun write(buff: FriendlyByteBuf) {
buff.writeInt(containerId) buff.writeInt(stackId)
buff.writeInt(id)
stack.write(buff) stack.write(buff)
} }
override fun play(context: Supplier<NetworkEvent.Context>) { override fun action(view: NetworkedItemView) {
context.get().packetHandled = true if (view.id2tuple.containsKey(stackId)) {
context.get().enqueueWork { throw IllegalStateException("NetworkedItemView $view already contains stack with id $stackId")
val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork
if (get.containerId != containerId)
return@enqueueWork
val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $containerId")
if (view.localState.containsKey(id)) {
throw IllegalStateException("Item tracker $containerId already has stack with id of $id")
}
val state = NetworkedItemView.NetworkedItem(id, stack)
view.localState[id] = state
/*val iterator = view.sortedView.iterator().withIndex()
var lastCompare = 0
var hit = false
while (iterator.hasNext()) {
val (i, existing) = iterator.next()
val cmp = view.sorter.compare(existing.stack, stack)
if (cmp != lastCompare && lastCompare != 0) {
hit = true
view.sortedView.add(i, state)
} else if (cmp != lastCompare) {
lastCompare = cmp
}
}
if (!hit) {*/
view.sortedView.add(state)
//}
view.resort()
} }
val tuple = NetworkedItemView.Tuple(stackId, stack)
view.id2tuple[stackId] = tuple
view.sortedView.addSorted(tuple, view.sorter.map(NetworkedItemView.Tuple::stack))
} }
companion object { companion object {
fun read(buffer: FriendlyByteBuf): StackAddPacket { fun read(buffer: FriendlyByteBuf): StackAddPacket {
val containerId = buffer.readInt()
val id = buffer.readInt() val id = buffer.readInt()
val item = StorageStack.ITEMS.read(buffer) val item = StorageStack.ITEMS.read(buffer)
return StackAddPacket(containerId, id, item) return StackAddPacket(id, item)
} }
} }
} }
class StackChangePacket(val id: Int, val stackID: Int, val newCount: BigInteger) : MatteryPacket { class StackChangePacket(val stackId: Int, val newCount: BigInteger) : NetworkedItemViewPacket() {
override fun write(buff: FriendlyByteBuf) { override fun write(buff: FriendlyByteBuf) {
buff.writeInt(id) buff.writeInt(stackId)
buff.writeInt(stackID)
buff.writeBigInteger(newCount) buff.writeBigInteger(newCount)
} }
override fun play(context: Supplier<NetworkEvent.Context>) { override fun action(view: NetworkedItemView) {
context.get().packetHandled = true val tuple = view.id2tuple[stackId] ?: throw IllegalStateException("No such stack with id $stackId in $view")
context.get().enqueueWork { tuple.stack = tuple.stack.copy(newCount)
val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork view.resort()
if (get.containerId != id)
return@enqueueWork
val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $id")
val state = view.localState[stackID] ?: throw IllegalStateException("No such stack with id $stackID in $view")
state.stack = state.stack.copy(newCount)
view.resort()
}
} }
companion object { companion object {
fun read(buffer: FriendlyByteBuf): StackChangePacket { fun read(buffer: FriendlyByteBuf): StackChangePacket {
val id = buffer.readInt()
val stackID = buffer.readInt() val stackID = buffer.readInt()
val newCount = buffer.readBigInteger() val newCount = buffer.readBigInteger()
return StackChangePacket(id, stackID, newCount) return StackChangePacket(stackID, newCount)
} }
} }
} }
class StackRemovePacket(val id: Int, val stackID: Int) : MatteryPacket { class StackRemovePacket(val stackId: Int) : NetworkedItemViewPacket() {
override fun write(buff: FriendlyByteBuf) { override fun write(buff: FriendlyByteBuf) {
buff.writeInt(id) buff.writeInt(stackId)
buff.writeInt(stackID)
} }
override fun play(context: Supplier<NetworkEvent.Context>) { override fun action(view: NetworkedItemView) {
context.get().packetHandled = true val obj = view.id2tuple.remove(stackId) ?: throw IllegalStateException("No such stack with id $stackId in $view")
context.get().enqueueWork { view.sortedView.remove(obj)
val get = Minecraft.getInstance().player?.containerMenu ?: return@enqueueWork view.resort()
if (get.containerId != id)
return@enqueueWork
val view = (get as? INetworkedItemViewProvider)?.networkedItemView ?: throw IllegalStateException("No such item tracker with id $id")
val obj = view.localState.remove(stackID) ?: throw IllegalStateException("No such stack with id $stackID in $view")
view.sortedView.remove(obj)
view.resort()
}
} }
companion object { companion object {
fun read(buffer: FriendlyByteBuf): StackRemovePacket { fun read(buffer: FriendlyByteBuf): StackRemovePacket {
val id = buffer.readInt()
val stackID = buffer.readInt() val stackID = buffer.readInt()
return StackRemovePacket(id, stackID) return StackRemovePacket(stackID)
} }
} }
} }
@ -198,129 +150,105 @@ class StackRemovePacket(val id: Int, val stackID: Int) : MatteryPacket {
/** /**
* Creates a virtual, slotless container for Player to interaction with. * Creates a virtual, slotless container for Player to interaction with.
*/ */
open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote: Boolean) : IStorageEventConsumer<ItemStorageStack> { class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val isRemote: Boolean) : IStorageEventConsumer<ItemStorageStack> {
data class NetworkedItem(val id: Int, var stack: ItemStorageStack, val upstreamId: UUID? = null) data class Tuple(val networkId: Int, var stack: ItemStorageStack, val upstreamId: UUID? = null) : Supplier<ItemStorageStack> {
override fun get(): ItemStorageStack {
return stack
}
}
override val storageType: StorageStack.Type<ItemStorageStack> override val storageType: StorageStack.Type<ItemStorageStack>
get() = StorageStack.ITEMS get() = StorageStack.ITEMS
// this (how client see and interact with) val id2tuple = Int2ObjectOpenHashMap<Tuple>()
val localState = Int2ObjectOpenHashMap<NetworkedItem>() val sortedView = ArrayList<Tuple>()
var sorter: Comparator<in ItemStorageStack> = ItemStorageStackSorter.DEFAULT
val sortedView = LinkedList<NetworkedItem>() set(value) {
var sorter: Comparator<ItemStack> = NAME_SORTER if (field != value) {
field = value
companion object { resort()
val NAME_SORTER = Comparator<ItemStack> { o1, o2 -> }
val cmp = o1.displayName.string.compareTo(o2.displayName.string)
if (cmp != 0)
return@Comparator cmp
return@Comparator o1.item.registryName.toString().compareTo(o2.item.registryName.toString())
} }
val COUNT_SORTER = Comparator<ItemStack> { o1, o2 -> val itemCount get() = id2tuple.size
val cmp = o1.count.compareTo(o2.count)
if (cmp != 0) private var nextItemID = 0
return@Comparator cmp private val uuid2tuple = Object2ObjectOpenHashMap<UUID, Tuple>()
private val networkBacklog = ArrayList<Any>()
return@Comparator o1.item.registryName.toString().compareTo(o2.item.registryName.toString()) operator fun get(id: Int): Tuple? = id2tuple[id]
}
}
fun resort() { fun resort() {
sortedView.sortWith { a, b -> if (isRemote) {
return@sortWith sorter.compare(a.stack.toItemStack(), b.stack.toItemStack()) sortedView.sortWith(sorter.map(Tuple::stack))
} }
} }
// parent (e.g. VirtualComponent) var component: IStorageComponent<ItemStorageStack>? = null
protected val upstreamState = HashMap<UUID, NetworkedItem>() set(provider) {
protected val networkBacklog = ArrayList<Any>() if (provider === field) return
operator fun get(id: Int): NetworkedItem? = localState[id] field?.removeListenerAndNotify(this)
field = provider
var provider: IStorageComponent<ItemStorageStack>? = null provider?.addListenerAndNotify(this)
private set }
fun mouseClick(index: Int, mouseButton: Int) { fun mouseClick(index: Int, mouseButton: Int) {
if (minecraft.player?.isSpectator == true) { if (minecraft.player?.isSpectator == true) return
return
}
val list = sortedView MenuNetworkChannel.sendToServer(ItemViewInteractPacket(
sortedView.getOrNull(index)?.networkId ?: -1,
val action = if (mouseButton == InputConstants.MOUSE_BUTTON_MIDDLE) ClickType.CLONE else if (Screen.hasShiftDown()) ClickType.QUICK_MOVE else ClickType.PICKUP,
if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) ClickAction.PRIMARY else ClickAction.SECONDARY if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) ClickAction.PRIMARY else ClickAction.SECONDARY
))
val type =
if (mouseButton == InputConstants.MOUSE_BUTTON_MIDDLE) ClickType.CLONE else if (Screen.hasShiftDown()) ClickType.QUICK_MOVE else ClickType.PICKUP
MenuNetworkChannel.sendToServer(
ItemViewInteractPacket(if (index >= list.size) -1 else list[index].id, type, action)
)
}
fun setComponent(provider: IStorageComponent<ItemStorageStack>?) {
if (provider === this.provider) return
this.provider?.removeListenerAndNotify(this)
this.provider = provider
provider?.addListenerAndNotify(this)
} }
fun removed() { fun removed() {
provider?.removeListenerAndNotify(this) component?.removeListenerAndNotify(this)
} }
val itemCount get() = localState.values.size
override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider<ItemStorageStack>) { override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider<ItemStorageStack>) {
check(!upstreamState.containsKey(id)) { "Already tracking ItemStack with upstream id $id!" } check(!uuid2tuple.containsKey(id)) { "Already tracking ItemStack with upstream id $id!" }
val state = NetworkedItem(nextItemID++, stack, id) val state = Tuple(nextItemID++, stack, id)
this.localState[state.id] = state this.id2tuple[state.networkId] = state
upstreamState[id] = state uuid2tuple[id] = state
network { StackAddPacket(menu.containerId, state.id, state.stack) } network { StackAddPacket(state.networkId, state.stack) }
} }
override fun onStackChanged(stack: ItemStorageStack, id: UUID) { override fun onStackChanged(stack: ItemStorageStack, id: UUID) {
val get = upstreamState[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!") val get = uuid2tuple[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!")
get.stack = stack get.stack = stack
network { StackChangePacket(menu.containerId, get.id, stack.count) } network { StackChangePacket(get.networkId, stack.count) }
} }
protected fun network(fn: () -> Any) { override fun onStackRemoved(id: UUID) {
if (!remote) { val get = uuid2tuple[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!")
uuid2tuple.remove(id)
id2tuple.remove(get.networkId)
network { StackRemovePacket(get.networkId) }
}
private inline fun network(fn: () -> Any) {
if (!isRemote) {
networkBacklog.add(fn()) networkBacklog.add(fn())
} }
} }
override fun onStackRemoved(id: UUID) {
val get = upstreamState[id] ?: throw IllegalStateException("Unknown ItemStack with upstream id $id!")
upstreamState.remove(id)
localState.remove(get.id)
network { StackRemovePacket(menu.containerId, get.id) }
}
protected var nextItemID = 0
fun clear() { fun clear() {
sortedView.clear() sortedView.clear()
upstreamState.clear() uuid2tuple.clear()
localState.clear() id2tuple.clear()
if (!remote) { if (!isRemote) {
networkBacklog.clear() networkBacklog.clear()
networkBacklog.add(ClearItemViewPacket) networkBacklog.add(ClearItemViewPacket)
} }
} }
fun network() { fun network() {
check(!remote) { "Not a server" } check(!isRemote) { "Not a server" }
val consumer = PacketDistributor.PLAYER.with { ply as ServerPlayer } val consumer = PacketDistributor.PLAYER.with { ply as ServerPlayer }
for (packet in networkBacklog) { for (packet in networkBacklog) {
@ -331,82 +259,41 @@ open class NetworkedItemView(val ply: Player, val menu: MatteryMenu, val remote:
} }
fun playerInteract(packet: ItemViewInteractPacket) { fun playerInteract(packet: ItemViewInteractPacket) {
val provider = provider ?: return val component = component ?: return
val (stackId, type, action) = packet
val click = packet.type
val action = packet.action
val stackId = packet.stackID
if (click == ClickType.CLONE) {
if (stackId < 0 || !ply.abilities.instabuild) return
val state = get(stackId) ?: return
val copy = state.stack.toItemStack().also { it.count = it.maxStackSize }
ply.containerMenu.carried = copy
MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(ply.containerMenu.carried))
ply.containerMenu.setRemoteCarried(ply.containerMenu.carried.copy())
if (type == ClickType.CLONE) {
if (!ply.abilities.instabuild) return
menu.syncCarried((get(stackId) ?: return).stack.toItemStack().also { it.count = it.maxStackSize })
return return
} }
if (click == ClickType.QUICK_MOVE && stackId > -1) { // забираем из системы с зажатым shift
val state = get(stackId) ?: return if (type == ClickType.QUICK_MOVE) {
val stack = state.stack.toItemStack() val tuple = get(stackId) ?: return
val stack = tuple.stack.toItemStack()
val amount = var amount = if (action == ClickAction.PRIMARY) all(stack) else half(stack)
if (action == ClickAction.PRIMARY) amount -= menu.quickMoveToInventory(tuple.stack.toItemStack(amount), true).count
stack.maxStackSize if (amount == 0) return
else
1.coerceAtLeast(stack.maxStackSize / 2)
val extracted = provider.extractStack(state.upstreamId!!, amount.toBigInteger(), true)
if (!extracted.isEmpty) {
val remaining = menu.quickMoveToInventory(extracted.toItemStack(), false)
if (remaining.count != extracted.count.toInt()) {
provider.extractStack(state.upstreamId, (extracted.count.toInt() - remaining.count).toBigInteger(), false)
}
}
menu.quickMoveToInventory(component.extractStack(tuple.upstreamId!!, amount.toBigInteger(), false).toItemStack(), false)
return return
} }
if (!menu.carried.isEmpty && click != ClickType.QUICK_MOVE) { if (menu.carried.isNotEmpty) {
// try to put
if (action == ClickAction.PRIMARY) { if (action == ClickAction.PRIMARY) {
val carried = menu.carried menu.syncCarried(component.insertStack(ItemStorageStack(menu.carried), false).toItemStack())
val amount = carried.count } else if (component.insertStack(ItemStorageStack(menu.carried.copyWithCount(1)), false).isEmpty) {
menu.carried.shrink(1)
if (amount == carried.count) { menu.syncCarried()
menu.carried = provider.insertStack(ItemStorageStack(menu.carried), false).toItemStack()
MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(menu.carried))
menu.setRemoteCarried(menu.carried.copy())
}
} else {
val copy = menu.carried.copy()
copy.count = 1
if (provider.insertStack(ItemStorageStack(copy), false).isEmpty) {
menu.carried.shrink(1)
MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(menu.carried))
menu.setRemoteCarried(menu.carried.copy())
}
} }
} else if (stackId > -1) { } else if (stackId > -1) {
val state = get(stackId) ?: return val state = get(stackId) ?: return
val stack = state.stack.toItemStack() val stack = state.stack.toItemStack()
val amount = if (action == ClickAction.PRIMARY) all(stack) else half(stack)
val amount = menu.carried = component.extractStack(state.upstreamId!!, amount.toBigInteger(), false).toItemStack()
if (action == ClickAction.PRIMARY) menu.syncCarried()
stack.maxStackSize
else
(stack.count / 2).coerceAtMost(stack.maxStackSize / 2).coerceAtLeast(1)
menu.carried = provider.extractStack(state.upstreamId!!, amount.toBigInteger(), false).toItemStack()
MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(menu.carried))
menu.setRemoteCarried(menu.carried.copy())
} }
} }
} }

View File

@ -3,7 +3,10 @@ package ru.dbotthepony.mc.otm.menu.input
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.ISubscriptable
import ru.dbotthepony.mc.otm.core.SentientGetterSetter
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.network.synchronizer.IField
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Predicate import java.util.function.Predicate
import java.util.function.Supplier import java.util.function.Supplier
@ -14,33 +17,7 @@ import kotlin.reflect.KMutableProperty0
* *
* Getting and setting values should ONLY be done clientside * Getting and setting values should ONLY be done clientside
*/ */
interface IPlayerInputWithFeedback<V> : GetterSetter<V>, Predicate<Player?> { interface IPlayerInputWithFeedback<V> : SentientGetterSetter<V>, Predicate<Player?>
companion object {
fun <V> of(getterSetter: GetterSetter<V>): IPlayerInputWithFeedback<V> {
return object : IPlayerInputWithFeedback<V>, GetterSetter<V> by getterSetter {
override fun test(t: Player?): Boolean {
return true
}
}
}
fun <V> of(getterSetter: GetterSetter<V>, filter: Predicate<Player?>): IPlayerInputWithFeedback<V> {
return object : IPlayerInputWithFeedback<V>, GetterSetter<V> by getterSetter, Predicate<Player?> by filter {}
}
fun <V> validPlayer(getterSetter: GetterSetter<V>): IPlayerInputWithFeedback<V> {
return object : IPlayerInputWithFeedback<V>, GetterSetter<V> by getterSetter {
override fun test(t: Player?): Boolean {
return t != null
}
}
}
}
}
fun <V> GetterSetter<V>.wrapAsPlayerInput(filter: Predicate<Player?> = Predicate { it != null }): IPlayerInputWithFeedback<V> {
return IPlayerInputWithFeedback.of(this, filter)
}
/** /**
* Represents Server to Client synchronization and Client to Server input * Represents Server to Client synchronization and Client to Server input
@ -49,13 +26,19 @@ fun <V> GetterSetter<V>.wrapAsPlayerInput(filter: Predicate<Player?> = Predicate
*/ */
abstract class AbstractPlayerInputWithFeedback<V> : IPlayerInputWithFeedback<V> { abstract class AbstractPlayerInputWithFeedback<V> : IPlayerInputWithFeedback<V> {
abstract val input: MatteryMenu.PlayerInput<V> abstract val input: MatteryMenu.PlayerInput<V>
abstract val value: V abstract val field: IField<V>
override fun get(): V { final override fun addListener(listener: Consumer<V>): ISubscriptable.L {
return field.addListener(listener)
}
val value: V get() = this.field.value
final override fun get(): V {
return value return value
} }
override fun accept(t: V) { final override fun accept(t: V) {
input.input(t) input.input(t)
} }

View File

@ -5,16 +5,28 @@ import ru.dbotthepony.mc.otm.menu.MatteryMenu
import java.util.function.BooleanSupplier import java.util.function.BooleanSupplier
import kotlin.reflect.KMutableProperty0 import kotlin.reflect.KMutableProperty0
class BooleanInputWithFeedback(menu: MatteryMenu) : AbstractPlayerInputWithFeedback<Boolean>() { class BooleanInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean = false) : AbstractPlayerInputWithFeedback<Boolean>() {
override val input = menu.booleanInput { consumer?.invoke(it) } override val input = menu.booleanInput(allowSpectators) { consumer?.invoke(it) }
override val value by menu.mSynchronizer.computedBool(BooleanSupplier { supplier?.invoke() ?: false }).property override val field = menu.mSynchronizer.computedBool(BooleanSupplier { supplier?.invoke() ?: false })
constructor(menu: MatteryMenu, state: KMutableProperty0<Boolean>) : this(menu) { constructor(menu: MatteryMenu, allowSpectators: Boolean, state: KMutableProperty0<Boolean>?) : this(menu, allowSpectators) {
with(state) if (state != null)
with(state)
} }
constructor(menu: MatteryMenu, state: GetterSetter<Boolean>) : this(menu) { constructor(menu: MatteryMenu, allowSpectators: Boolean, state: GetterSetter<Boolean>?) : this(menu, allowSpectators) {
with(state) if (state != null)
with(state)
}
constructor(menu: MatteryMenu, state: KMutableProperty0<Boolean>?) : this(menu) {
if (state != null)
with(state)
}
constructor(menu: MatteryMenu, state: GetterSetter<Boolean>?) : this(menu) {
if (state != null)
with(state)
} }
fun switchValue() { fun switchValue() {

View File

@ -9,12 +9,16 @@ inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu) = Enum
inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, state: KMutableProperty0<E>?) = EnumInputWithFeedback(menu, E::class.java, state) inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, state: KMutableProperty0<E>?) = EnumInputWithFeedback(menu, E::class.java, state)
inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, state: GetterSetter<E>) = EnumInputWithFeedback(menu, E::class.java, state) inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, state: GetterSetter<E>) = EnumInputWithFeedback(menu, E::class.java, state)
class EnumInputWithFeedback<E : Enum<E>>(menu: MatteryMenu, clazz: Class<E>) : AbstractPlayerInputWithFeedback<E>() { inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean) = EnumInputWithFeedback(menu, E::class.java, allowSpectators)
inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean, state: KMutableProperty0<E>?) = EnumInputWithFeedback(menu, E::class.java, allowSpectators, state)
inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, allowSpectators: Boolean, state: GetterSetter<E>) = EnumInputWithFeedback(menu, E::class.java, allowSpectators, state)
class EnumInputWithFeedback<E : Enum<E>>(menu: MatteryMenu, clazz: Class<E>, allowSpectators: Boolean = false) : AbstractPlayerInputWithFeedback<E>() {
val codec = EnumValueCodec(clazz) val codec = EnumValueCodec(clazz)
private val default = codec.values.first() private val default = codec.values.first()
override val input = menu.PlayerInput(codec, false) { consumer?.invoke(it) } override val input = menu.PlayerInput(codec, allowSpectators) { consumer?.invoke(it) }
override val value by menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec) override val field = menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec)
constructor(menu: MatteryMenu, clazz: Class<E>, state: KMutableProperty0<E>?) : this(menu, clazz) { constructor(menu: MatteryMenu, clazz: Class<E>, state: KMutableProperty0<E>?) : this(menu, clazz) {
if (state != null) { if (state != null) {
@ -22,7 +26,21 @@ class EnumInputWithFeedback<E : Enum<E>>(menu: MatteryMenu, clazz: Class<E>) : A
} }
} }
constructor(menu: MatteryMenu, clazz: Class<E>, state: GetterSetter<E>) : this(menu, clazz) { constructor(menu: MatteryMenu, clazz: Class<E>, state: GetterSetter<E>?) : this(menu, clazz) {
with(state) if (state != null) {
with(state)
}
}
constructor(menu: MatteryMenu, clazz: Class<E>, allowSpectators: Boolean, state: KMutableProperty0<E>?) : this(menu, clazz, allowSpectators) {
if (state != null) {
with(state)
}
}
constructor(menu: MatteryMenu, clazz: Class<E>, allowSpectators: Boolean, state: GetterSetter<E>?) : this(menu, clazz, allowSpectators) {
if (state != null) {
with(state)
}
} }
} }

View File

@ -6,7 +6,7 @@ import kotlin.reflect.KMutableProperty0
class StringInputWithFeedback(menu: MatteryMenu) : AbstractPlayerInputWithFeedback<String>() { class StringInputWithFeedback(menu: MatteryMenu) : AbstractPlayerInputWithFeedback<String>() {
override val input = menu.stringInput { consumer?.invoke(it.replace('\u0000', ' ')) } override val input = menu.stringInput { consumer?.invoke(it.replace('\u0000', ' ')) }
override val value by menu.mSynchronizer.computedString { supplier?.invoke() ?: "" } override val field = menu.mSynchronizer.computedString { supplier?.invoke() ?: "" }
constructor(menu: MatteryMenu, state: KMutableProperty0<String>) : this(menu) { constructor(menu: MatteryMenu, state: KMutableProperty0<String>) : this(menu) {
with(state) with(state)

View File

@ -190,8 +190,8 @@ class MatterPanelMenu(
val sortingGS = GetterSetter.of(::sorting, changeSorting::input) val sortingGS = GetterSetter.of(::sorting, changeSorting::input)
val isAscendingGS = GetterSetter.of(::isAscending, changeIsAscending::input) val isAscendingGS = GetterSetter.of(::isAscending, changeIsAscending::input)
private val actualComparator = Comparator<PatternState> { o1, o2 -> sorting.comparator.compare(o1.item, o2.item) * (if (isAscending) 1 else -1) } private val actualComparator = Comparator<PatternState> { o1, o2 -> sorting.compare(o1.item, o2.item) * (if (isAscending) 1 else -1) }
private val actualTaskComparator = Comparator<ReplicationTask> { o1, o2 -> sorting.comparator.compare(o1.item, o2.item) * (if (isAscending) 1 else -1) } private val actualTaskComparator = Comparator<ReplicationTask> { o1, o2 -> sorting.compare(o1.item, o2.item) * (if (isAscending) 1 else -1) }
private val patterns = ArrayList<PatternState>() private val patterns = ArrayList<PatternState>()
private val tasks = ArrayList<ReplicationTask>() private val tasks = ArrayList<ReplicationTask>()

View File

@ -1,57 +1,67 @@
package ru.dbotthepony.mc.otm.menu.storage package ru.dbotthepony.mc.otm.menu.storage
import com.google.common.collect.ImmutableList
import net.minecraft.world.SimpleContainer import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.capabilities.ForgeCapabilities import net.minecraftforge.common.capabilities.ForgeCapabilities
import ru.dbotthepony.mc.otm.block.entity.storage.DriveViewerBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.DriveViewerBlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.core.ifPresentK import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider
import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.registry.MMenus import ru.dbotthepony.mc.otm.registry.MMenus
import ru.dbotthepony.mc.otm.storage.ItemStorageStack import ru.dbotthepony.mc.otm.storage.ItemStorageStack
import ru.dbotthepony.mc.otm.storage.StorageStack import ru.dbotthepony.mc.otm.storage.StorageStack
import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent
class DriveViewerMenu @JvmOverloads constructor( class DriveViewerMenu(
containerID: Int, containerID: Int,
inventory: Inventory, inventory: Inventory,
tile: DriveViewerBlockEntity? = null tile: DriveViewerBlockEntity? = null
) : MatteryPoweredMenu( ) : MatteryPoweredMenu(MMenus.DRIVE_VIEWER, containerID, inventory, tile), INetworkedItemViewProvider {
MMenus.DRIVE_VIEWER, containerID, inventory, tile
), INetworkedItemViewProvider {
override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null) override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null)
val driveSlot: MatterySlot
val driveSlot = object : MatterySlot(tile?.container ?: SimpleContainer(1), 0) {
override fun mayPlace(itemStack: ItemStack): Boolean {
return itemStack.getCapability(MatteryCapability.DRIVE).isPresent
}
}
private val powered: PoweredVirtualComponent<ItemStorageStack>? private val powered: PoweredVirtualComponent<ItemStorageStack>?
private var lastDrive: IMatteryDrive<ItemStorageStack>? = null private var lastDrive: IMatteryDrive<ItemStorageStack>? = null
val profiledEnergy = ProfiledLevelGaugeWidget<ProfiledEnergyStorage<*>>(this, energyWidget)
val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
init { val settings = object : DriveViewerBlockEntity.ISettings {
val container = tile?.container ?: SimpleContainer(1) override var sorting: ItemStorageStackSorter by EnumInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(ply)::sorting }).also { it.addListener { changes() } }
override var isAscending: Boolean by BooleanInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(ply)::isAscending }).also { it.addListener { changes() } }
driveSlot = object : MatterySlot(container, 0) { private fun changes() {
override fun mayPlace(itemStack: ItemStack): Boolean { if (isAscending) {
return itemStack.getCapability(MatteryCapability.DRIVE).isPresent networkedItemView.sorter = sorting
} else {
networkedItemView.sorter = sorting.reversed
} }
} }
}
init {
if (tile != null) { if (tile != null) {
powered = PoweredVirtualComponent( profiledEnergy.with(tile.energy)
StorageStack.ITEMS, powered = PoweredVirtualComponent(StorageStack.ITEMS, tile::energy)
tile.getCapability(MatteryCapability.ENERGY).resolve()::get this.networkedItemView.component = powered
)
this.networkedItemView.setComponent(powered)
} else { } else {
powered = null powered = null
} }
@ -126,12 +136,9 @@ class DriveViewerMenu @JvmOverloads constructor(
} }
} }
override fun removed(p_38940_: Player) { override fun removed(player: Player) {
super.removed(p_38940_) super.removed(player)
lastDrive?.let { powered?.remove(it) }
if (lastDrive != null)
powered?.remove(lastDrive!!)
this.networkedItemView.removed() this.networkedItemView.removed()
} }

View File

@ -6,15 +6,20 @@ import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraftforge.network.PacketDistributor import ru.dbotthepony.mc.otm.block.entity.storage.IItemMonitorPlayerSettings
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorBlockEntity import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorBlockEntity
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings
import ru.dbotthepony.mc.otm.container.get import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.collect.mapToInt
import ru.dbotthepony.mc.otm.core.collect.reduce
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider
import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView
import ru.dbotthepony.mc.otm.network.MenuNetworkChannel import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
import ru.dbotthepony.mc.otm.menu.makeSlots
import ru.dbotthepony.mc.otm.registry.MMenus import ru.dbotthepony.mc.otm.registry.MMenus
import ru.dbotthepony.mc.otm.storage.* import ru.dbotthepony.mc.otm.storage.*
import java.util.* import java.util.*
@ -73,73 +78,85 @@ private class ResultSlot(container: Container) : MatterySlot(container, 0) {
} }
} }
class ItemMonitorMenu @JvmOverloads constructor( class ItemMonitorMenu(
containerId: Int, containerId: Int,
inventory: Inventory, inventory: Inventory,
tile: ItemMonitorBlockEntity? = null tile: ItemMonitorBlockEntity? = null
) : MatteryPoweredMenu(MMenus.ITEM_MONITOR, containerId, inventory, tile), INetworkedItemViewProvider { ) : MatteryPoweredMenu(MMenus.ITEM_MONITOR, containerId, inventory, tile), INetworkedItemViewProvider {
override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null) override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null)
val settings: ItemMonitorPlayerSettings = tile?.getSettings(inventory.player as ServerPlayer) ?: ItemMonitorPlayerSettings()
private var settingsNetworked = false val settings = object : IItemMonitorPlayerSettings {
override var ingredientPriority by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::ingredientPriority })
override var resultTarget by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::resultTarget })
override var craftingAmount by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::craftingAmount })
override var sorting by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::sorting }).also { it.addListener { changes() } }
override var ascendingSort by BooleanInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(ply as ServerPlayer)::ascendingSort }).also { it.addListener { changes() } }
private fun changes() {
if (ascendingSort) {
networkedItemView.sorter = sorting
} else {
networkedItemView.sorter = sorting.reversed
}
}
}
val craftingResult: MatterySlot val craftingResult: MatterySlot
val craftingSlots: List<MatterySlot> val craftingSlots: List<MatterySlot>
init { init {
if (tile != null) { if (tile != null) {
networkedItemView.setComponent(tile.poweredView) networkedItemView.component = tile.poweredView
craftingResult = ResultSlot(tile.craftingResultContainer)
craftingSlots = Collections.unmodifiableList(Array(9) { MatterySlot(tile.craftingGrid, it) }.asList())
} else {
craftingResult = ResultSlot(SimpleContainer(1))
val container = SimpleContainer(9)
craftingSlots = Collections.unmodifiableList(Array(9) { MatterySlot(container, it) }.asList())
} }
val slots = craftingSlots.map(this::addSlot) craftingResult = ResultSlot(tile?.craftingResultContainer ?: SimpleContainer(1))
craftingSlots = makeSlots(tile?.craftingGrid ?: SimpleContainer(9), ::MatterySlot)
craftingSlots.forEach(this::addSlot)
addSlot(craftingResult) addSlot(craftingResult)
addInventorySlots() addInventorySlots()
} }
override fun broadcastFullState() { override fun removed(player: Player) {
super.broadcastFullState() super.removed(player)
MenuNetworkChannel.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings)
settingsNetworked = true
}
fun sendSettingsToServer() {
MenuNetworkChannel.sendToServer(settings)
}
override fun removed(p_38940_: Player) {
super.removed(p_38940_)
networkedItemView.removed() networkedItemView.removed()
} }
override fun broadcastChanges() { override fun broadcastChanges() {
super.broadcastChanges() super.broadcastChanges()
networkedItemView.network() networkedItemView.network()
}
if (!settingsNetworked) { private fun moveCrafting(view: IStorageComponent<ItemStorageStack>, simulate: Boolean): Boolean {
MenuNetworkChannel.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings) val itemStack = craftingResult.item
settingsNetworked = true var remaining: ItemStack
if (settings.resultTarget == ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM) {
remaining = view.insertStack(ItemStorageStack(itemStack), simulate).toItemStack()
remaining = moveItemStackToSlots(remaining, playerInventorySlots, simulate)
} else {
remaining = moveItemStackToSlots(itemStack, playerInventorySlots, simulate)
remaining = view.insertStack(ItemStorageStack(remaining), simulate).toItemStack()
} }
if (!simulate && remaining.isNotEmpty) {
ply.spawnAtLocation(remaining)
}
if (!simulate) {
craftingResult.remove(itemStack.count)
}
return remaining.count != itemStack.count
} }
override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack { override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack {
if (playerInventorySlots.any { it.index == slotIndex }) { if (playerInventorySlots.any { it.index == slotIndex }) {
if (tile == null) { if (tile == null) return ItemStack.EMPTY
return ItemStack.EMPTY
}
val slot = slots[slotIndex] val slot = slots[slotIndex]
if (slot.item.isEmpty) return ItemStack.EMPTY
if (slot.item.isEmpty) { val leftover = networkedItemView.component?.insertStack(ItemStorageStack(slot.item), false)?.toItemStack() ?: slot.item
return ItemStack.EMPTY
}
val leftover = networkedItemView.provider?.insertStack(ItemStorageStack(slot.item), false)?.toItemStack() ?: slot.item
if (leftover.count == slot.item.count) { if (leftover.count == slot.item.count) {
return ItemStack.EMPTY return ItemStack.EMPTY
@ -148,77 +165,48 @@ class ItemMonitorMenu @JvmOverloads constructor(
val old = slot.item.copy() val old = slot.item.copy()
slot.item.count = leftover.count slot.item.count = leftover.count
return old return old
} else if (slotIndex in craftingSlots[0].index .. craftingSlots[craftingSlots.size - 1].index) { } else if (craftingSlots.any { it.index == slotIndex }) {
// from crafting grid to inventory // from crafting grid to inventory
val item = slots[slotIndex].item val item = slots[slotIndex].item
if (item.isEmpty) return ItemStack.EMPTY
if (item.isEmpty) { var remainder = item
return ItemStack.EMPTY
when (settings.ingredientPriority) {
ItemMonitorPlayerSettings.IngredientPriority.SYSTEM, ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST -> {
remainder = networkedItemView.component?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder
}
else -> {}
} }
var remainder = moveItemStackToSlots(item, playerInventorySlots) remainder = moveItemStackToSlots(remainder, playerInventorySlots)
slots[slotIndex].set(remainder) slots[slotIndex].set(remainder)
if (remainder.isEmpty) {
return item
}
remainder = networkedItemView.provider?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder
slots[slotIndex].set(remainder)
if (remainder.isEmpty) {
return item
}
return if (remainder.count != item.count) item else ItemStack.EMPTY return if (remainder.count != item.count) item else ItemStack.EMPTY
} else if (slotIndex == craftingResult.index) { } else if (slotIndex == craftingResult.index) {
// quickcraft... god damn it var item = craftingResult.item
if (!craftingResult.hasItem()) { if (item.isEmpty) return ItemStack.EMPTY
return ItemStack.EMPTY
}
val item = craftingResult.item
val tile = tile as ItemMonitorBlockEntity? ?: return ItemStack.EMPTY val tile = tile as ItemMonitorBlockEntity? ?: return ItemStack.EMPTY
if (tile.lastCraftingRecipe(ply) != null && tile.craftingRecipe != tile.lastCraftingRecipe(ply)) { if (tile.lastCraftingRecipe(ply) != null && tile.craftingRecipe != tile.lastCraftingRecipe(ply)) {
// recipe has changed // рецепт изменился
return ItemStack.EMPTY return ItemStack.EMPTY
} }
val crafted = tile.howMuchPlayerCrafted(ply)
if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.ONE) { if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.ONE) {
if (tile.howMuchPlayerCrafted(ply) > 0) { if (crafted > 0) {
return ItemStack.EMPTY return ItemStack.EMPTY
} }
} else { } else {
var hasUnstackables = false if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.STACK || tile.craftingGrid.any { !it.isStackable }) {
var maxStack = 64 if (crafted > 0 && (crafted + 1) * item.count > item.maxStackSize) {
if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.FULL) {
for (gridItem in tile.craftingGrid.iterator()) {
if (!gridItem.isStackable) {
hasUnstackables = true
break
}
}
} else {
maxStack = 0
for (gridItem in tile.craftingGrid.iterator()) {
maxStack = maxStack.coerceAtLeast(gridItem.maxStackSize)
}
}
if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.STACK || hasUnstackables) {
val count = tile.howMuchPlayerCrafted(ply)
if (count > 0 && (count + 1) * craftingResult.item.count > craftingResult.item.maxStackSize) {
return ItemStack.EMPTY return ItemStack.EMPTY
} }
} else { } else {
val count = tile.howMuchPlayerCrafted(ply) if (crafted > 0 && crafted >= tile.craftingGrid.iterator().mapToInt { it.maxStackSize }.reduce(Int.MAX_VALUE, Int::coerceAtMost)) {
if (count > 0 && count >= maxStack) {
return ItemStack.EMPTY return ItemStack.EMPTY
} }
} }
@ -227,50 +215,12 @@ class ItemMonitorMenu @JvmOverloads constructor(
tile.craftingResultContainer.craftingPlayer = ply as ServerPlayer tile.craftingResultContainer.craftingPlayer = ply as ServerPlayer
try { try {
when (settings.resultTarget) { if (moveCrafting(tile.poweredView ?: return ItemStack.EMPTY, true)) {
ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM -> { item = item.copy()
val wrapper = ItemStorageStack(item) moveCrafting(tile.poweredView ?: return ItemStack.EMPTY, false)
var remaining = tile.poweredView?.insertStack(wrapper, true)?.toItemStack() ?: return ItemStack.EMPTY return item
} else {
if (remaining.isEmpty) { return ItemStack.EMPTY
tile.poweredView!!.insertStack(wrapper, false)
craftingResult.remove(item.count)
return item
}
remaining = moveItemStackToSlots(remaining, playerInventorySlots, simulate = true)
if (remaining.isEmpty) {
remaining = tile.poweredView!!.insertStack(wrapper, false).toItemStack()
moveItemStackToSlots(remaining, playerInventorySlots, simulate = false)
craftingResult.remove(item.count)
return item
}
return ItemStack.EMPTY
}
ItemMonitorPlayerSettings.ResultTarget.MIXED, ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY -> {
var remaining = moveItemStackToSlots(item, playerInventorySlots, simulate = true)
if (remaining.isEmpty) {
moveItemStackToSlots(item, playerInventorySlots, simulate = false)
craftingResult.remove(item.count)
return item
}
val wrapper = ItemStorageStack(remaining)
remaining = tile.poweredView?.insertStack(wrapper, true)?.toItemStack() ?: return ItemStack.EMPTY
if (remaining.isEmpty) {
moveItemStackToSlots(item, playerInventorySlots, simulate = false)
tile.poweredView!!.insertStack(wrapper, false)
craftingResult.remove(item.count)
return item
}
return ItemStack.EMPTY
}
} }
} finally { } finally {
tile.craftingResultContainer.craftingPlayer = null tile.craftingResultContainer.craftingPlayer = null

View File

@ -81,8 +81,8 @@ object MenuNetworkChannel : MatteryNetworkChannel(
add(SetCarriedPacket::class.java, SetCarriedPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(SetCarriedPacket::class.java, SetCarriedPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
add(ItemFilterSlotPacket::class.java, ItemFilterSlotPacket.Companion::read) add(ItemFilterSlotPacket::class.java, ItemFilterSlotPacket.Companion::read)
// networked view // networked item view
add(ClearItemViewPacket::class.java, ClearItemViewPacket::read, NetworkDirection.PLAY_TO_CLIENT) add(ClearItemViewPacket::class.java, { ClearItemViewPacket }, NetworkDirection.PLAY_TO_CLIENT)
add(ItemViewInteractPacket::class.java, ItemViewInteractPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) add(ItemViewInteractPacket::class.java, ItemViewInteractPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
add(StackAddPacket::class.java, StackAddPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(StackAddPacket::class.java, StackAddPacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
add(StackChangePacket::class.java, StackChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(StackChangePacket::class.java, StackChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)
@ -94,9 +94,6 @@ object MenuNetworkChannel : MatteryNetworkChannel(
// Client->Server // Client->Server
add(MatteryMenu.PlayerInputPacket::class.java, MatteryMenu::PlayerInputPacket, NetworkDirection.PLAY_TO_SERVER) add(MatteryMenu.PlayerInputPacket::class.java, MatteryMenu::PlayerInputPacket, NetworkDirection.PLAY_TO_SERVER)
// Item monitor
add(ItemMonitorPlayerSettings::class.java, ItemMonitorPlayerSettings.Companion::read)
// matter panel menu // matter panel menu
add(CancelTaskPacket::class.java, CancelTaskPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER) add(CancelTaskPacket::class.java, CancelTaskPacket.Companion::read, NetworkDirection.PLAY_TO_SERVER)
add(PatternsChangePacket::class.java, PatternsChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT) add(PatternsChangePacket::class.java, PatternsChangePacket.Companion::read, NetworkDirection.PLAY_TO_CLIENT)

View File

@ -33,6 +33,7 @@ import java.lang.ref.WeakReference
import java.math.BigDecimal import java.math.BigDecimal
import java.util.* import java.util.*
import java.util.function.BooleanSupplier import java.util.function.BooleanSupplier
import java.util.function.Consumer
import java.util.function.DoubleConsumer import java.util.function.DoubleConsumer
import java.util.function.DoubleSupplier import java.util.function.DoubleSupplier
import java.util.function.IntConsumer import java.util.function.IntConsumer
@ -529,6 +530,11 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
isObserver: Boolean = false, isObserver: Boolean = false,
) : AbstractField<V>(), IMutableField<V> { ) : AbstractField<V>(), IMutableField<V> {
private var remote: V = codec.copy(field) private var remote: V = codec.copy(field)
private val subs = ISubscriptable.Impl<V>()
override fun addListener(listener: Consumer<V>): ISubscriptable.L {
return subs.addListener(listener)
}
init { init {
if (isObserver) { if (isObserver) {
@ -548,6 +554,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
this@Field.field = value this@Field.field = value
subs.accept(value)
} }
} }
@ -577,15 +584,15 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
if (setter != null) { if (setter != null) {
setter.invoke(value, access, false) setter.invoke(value, access, false)
return } else {
} if (!isDirty && !codec.compare(remote, value)) {
notifyEndpoints(this@Field)
isDirty = true
}
if (!isDirty && !codec.compare(remote, value)) { this.field = value
notifyEndpoints(this@Field) subs.accept(value)
isDirty = true
} }
this.field = value
} }
override fun write(stream: DataOutputStream, endpoint: Endpoint) { override fun write(stream: DataOutputStream, endpoint: Endpoint) {
@ -602,10 +609,10 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
if (setter != null) { if (setter != null) {
setter.invoke(value, access, true) setter.invoke(value, access, true)
return } else {
this.field = value
subs.accept(value)
} }
this.field = value
} }
} }
@ -619,7 +626,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
/** /**
* Type specific field, storing primitive [Float] directly * Type specific field, storing primitive [Float] directly
*/ */
inner class FloatField(private var field: Float, private val getter: FloatFieldGetter? = null, private val setter: FloatFieldSetter? = null) : PrimitiveField<Float>(), IMutableFloatField { inner class FloatField(field: Float, private val getter: FloatFieldGetter? = null, private val setter: FloatFieldSetter? = null) : PrimitiveField<Float>(), IMutableFloatField {
private val subs = IFloatSubcripable.Impl()
override fun addListener(listener: FloatConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
private var field = field
set(value) {
if (field != value) {
field = value
subs.accept(value)
}
}
override val property = object : IMutableFloatProperty { override val property = object : IMutableFloatProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Float { override fun getValue(thisRef: Any?, property: KProperty<*>): Float {
return this@FloatField.float return this@FloatField.float
@ -686,7 +707,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
/** /**
* Type specific field, storing primitive [Double] directly * Type specific field, storing primitive [Double] directly
*/ */
inner class DoubleField(private var field: Double, private val getter: DoubleFieldGetter? = null, private val setter: DoubleFieldSetter? = null) : PrimitiveField<Double>(), IMutableDoubleField { inner class DoubleField(field: Double, private val getter: DoubleFieldGetter? = null, private val setter: DoubleFieldSetter? = null) : PrimitiveField<Double>(), IMutableDoubleField {
private val subs = IDoubleSubcripable.Impl()
override fun addListener(listener: DoubleConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
private var field = field
set(value) {
if (field != value) {
field = value
subs.accept(value)
}
}
override val property = object : IMutableDoubleProperty { override val property = object : IMutableDoubleProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Double { override fun getValue(thisRef: Any?, property: KProperty<*>): Double {
return this@DoubleField.double return this@DoubleField.double
@ -750,7 +785,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
} }
abstract inner class AbstractIntField(protected var field: Int, private val getter: IntFieldGetter? = null, protected val setter: IntFieldSetter? = null) : PrimitiveField<Int>(), IMutableIntField { abstract inner class AbstractIntField(field: Int, private val getter: IntFieldGetter? = null, protected val setter: IntFieldSetter? = null) : PrimitiveField<Int>(), IMutableIntField {
private val subs = IIntSubcripable.Impl()
override fun addListener(listener: IntConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
protected var field = field
set(value) {
if (field != value) {
field = value
subs.accept(value)
}
}
final override val property = object : IMutableIntProperty { final override val property = object : IMutableIntProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int { override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return this@AbstractIntField.int return this@AbstractIntField.int
@ -845,7 +894,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
/** /**
* Type specific field, storing primitive [Long] directly * Type specific field, storing primitive [Long] directly
*/ */
abstract inner class AbstractLongField(protected var field: Long, private val getter: LongFieldGetter? = null, protected val setter: LongFieldSetter? = null) : PrimitiveField<Long>(), IMutableLongField { abstract inner class AbstractLongField(field: Long, private val getter: LongFieldGetter? = null, protected val setter: LongFieldSetter? = null) : PrimitiveField<Long>(), IMutableLongField {
private val subs = ILongSubcripable.Impl()
override fun addListener(listener: LongConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
protected var field = field
set(value) {
if (field != value) {
field = value
subs.accept(value)
}
}
final override val property = object : IMutableLongProperty { final override val property = object : IMutableLongProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Long { override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
return this@AbstractLongField.long return this@AbstractLongField.long
@ -940,7 +1003,21 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
/** /**
* Type specific field, storing primitive [Boolean] directly * Type specific field, storing primitive [Boolean] directly
*/ */
inner class BooleanField(private var field: Boolean, private val getter: BooleanFieldGetter? = null, private val setter: BooleanFieldSetter? = null) : PrimitiveField<Boolean>(), IMutableBooleanField { inner class BooleanField(field: Boolean, private val getter: BooleanFieldGetter? = null, private val setter: BooleanFieldSetter? = null) : PrimitiveField<Boolean>(), IMutableBooleanField {
private val subs = IBooleanSubscriptable.Impl()
override fun addListener(listener: BooleanConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
private var field = field
set(value) {
if (field != value) {
field = value
subs.accept(value)
}
}
override val property = object : IMutableBooleanProperty { override val property = object : IMutableBooleanProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
return this@BooleanField.boolean return this@BooleanField.boolean
@ -1014,6 +1091,11 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
) : AbstractField<V>(), IField<V> { ) : AbstractField<V>(), IField<V> {
private var remote: Any? = Mark private var remote: Any? = Mark
private var clientValue: Any? = Mark private var clientValue: Any? = Mark
private val subs = ISubscriptable.Impl<V>()
override fun addListener(listener: Consumer<V>): ISubscriptable.L {
return subs.addListener(listener)
}
init { init {
observers.add(this) observers.add(this)
@ -1055,6 +1137,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
val newValue = codec.read(stream) val newValue = codec.read(stream)
clientValue = newValue clientValue = newValue
observer.invoke(newValue) observer.invoke(newValue)
subs.accept(newValue)
} }
} }
@ -1063,11 +1146,22 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
* *
* This class has concrete implementation for [Float] primitive * This class has concrete implementation for [Float] primitive
*/ */
inner class ComputedFloatField(private val getter: FloatSupplier, private val observer: FloatConsumer = FloatConsumer {}) : AbstractField<Float>(), IFloatField { inner class ComputedFloatField(private val getter: FloatSupplier, observer: FloatConsumer? = null) : AbstractField<Float>(), IFloatField {
private var remote: Float = 0f private var remote: Float = 0f
private var isRemoteSet = false private var isRemoteSet = false
private var clientValue: Float = 0f private var clientValue: Float = 0f
private var isClientValue = false private var isClientValue = false
private val subs = IFloatSubcripable.Impl()
init {
if (observer != null) {
subs.addListener(observer)
}
}
override fun addListener(listener: FloatConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
override val property = object : IFloatProperty { override val property = object : IFloatProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Float { override fun getValue(thisRef: Any?, property: KProperty<*>): Float {
@ -1120,7 +1214,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
val newValue = stream.readFloat() val newValue = stream.readFloat()
clientValue = newValue clientValue = newValue
isClientValue = true isClientValue = true
observer.accept(newValue) subs.accept(newValue)
} }
@Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property"))
@ -1134,11 +1228,22 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
* *
* This class has concrete implementation for [Double] primitive * This class has concrete implementation for [Double] primitive
*/ */
inner class ComputedDoubleField(private val getter: DoubleSupplier, private val observer: DoubleConsumer = DoubleConsumer {}) : AbstractField<Double>(), IDoubleField { inner class ComputedDoubleField(private val getter: DoubleSupplier, observer: DoubleConsumer? = null) : AbstractField<Double>(), IDoubleField {
private var remote: Double = 0.0 private var remote: Double = 0.0
private var isRemoteSet = false private var isRemoteSet = false
private var clientValue: Double = 0.0 private var clientValue: Double = 0.0
private var isClientValue = false private var isClientValue = false
private val subs = IDoubleSubcripable.Impl()
init {
if (observer != null) {
subs.addListener(observer)
}
}
override fun addListener(listener: DoubleConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
override val property = object : IDoubleProperty { override val property = object : IDoubleProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Double { override fun getValue(thisRef: Any?, property: KProperty<*>): Double {
@ -1185,7 +1290,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
val newValue = stream.readDouble() val newValue = stream.readDouble()
clientValue = newValue clientValue = newValue
isClientValue = true isClientValue = true
observer.accept(newValue) subs.accept(newValue)
} }
@Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property"))
@ -1199,11 +1304,29 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
* *
* This class has concrete implementation for [Int] primitive * This class has concrete implementation for [Int] primitive
*/ */
abstract inner class AbstractComputedIntField(protected val getter: IntSupplier, protected val observer: IntConsumer = IntConsumer {}) : AbstractField<Int>(), IIntField { abstract inner class AbstractComputedIntField(protected val getter: IntSupplier, observer: IntConsumer? = null) : AbstractField<Int>(), IIntField {
private var remote: Int = 0 private var remote: Int = 0
private var isRemoteSet = false private var isRemoteSet = false
protected var clientValue: Int = 0 protected var clientValue: Int = 0
set(value) {
if (field != value) {
field = value
subs.accept(value)
}
}
protected var isClientValue = false protected var isClientValue = false
private val subs = IIntSubcripable.Impl()
init {
if (observer != null) {
subs.addListener(observer)
}
}
override fun addListener(listener: IntConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
final override val property = object : IIntProperty { final override val property = object : IIntProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int { override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
@ -1259,10 +1382,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
override fun read(stream: DataInputStream) { override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" } check(!isRemoved) { "Field was removed" }
val newValue = stream.readVarIntLE() clientValue = stream.readVarIntLE()
clientValue = newValue
isClientValue = true
observer.accept(newValue)
} }
} }
@ -1280,10 +1400,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
override fun read(stream: DataInputStream) { override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" } check(!isRemoved) { "Field was removed" }
val newValue = stream.readInt() clientValue = stream.readInt()
clientValue = newValue
isClientValue = true
observer.accept(newValue)
} }
} }
@ -1292,11 +1409,31 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
* *
* This class has concrete implementation for [Long] primitive * This class has concrete implementation for [Long] primitive
*/ */
abstract inner class AbstractComputedLongField(protected val getter: LongSupplier, protected val observer: LongConsumer = LongConsumer {}) : AbstractField<Long>(), ILongField { abstract inner class AbstractComputedLongField(protected val getter: LongSupplier, observer: LongConsumer? = null) : AbstractField<Long>(), ILongField {
private var remote: Long = 0L private var remote: Long = 0L
private var isRemoteSet = false private var isRemoteSet = false
protected var clientValue: Long = 0L protected var clientValue: Long = 0L
set(value) {
isClientValue = true
if (field != value) {
field = value
subs.accept(value)
}
}
protected var isClientValue = false protected var isClientValue = false
private val subs = ILongSubcripable.Impl()
init {
if (observer != null) {
subs.addListener(observer)
}
}
override fun addListener(listener: LongConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
final override val property = object : ILongProperty { final override val property = object : ILongProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Long { override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
@ -1352,10 +1489,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
override fun read(stream: DataInputStream) { override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" } check(!isRemoved) { "Field was removed" }
val newValue = stream.readVarLongLE() clientValue = stream.readVarLongLE()
clientValue = newValue
isClientValue = true
observer.accept(newValue)
} }
} }
@ -1373,10 +1507,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
override fun read(stream: DataInputStream) { override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" } check(!isRemoved) { "Field was removed" }
val newValue = stream.readLong() clientValue = stream.readLong()
clientValue = newValue
isClientValue = true
observer.accept(newValue)
} }
} }
@ -1385,11 +1516,22 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
* *
* This class has concrete implementation for [Boolean] primitive * This class has concrete implementation for [Boolean] primitive
*/ */
inner class ComputedBooleanField(private val getter: BooleanSupplier, private val observer: BooleanConsumer = BooleanConsumer {}) : AbstractField<Boolean>(), IBooleanField { inner class ComputedBooleanField(private val getter: BooleanSupplier, observer: BooleanConsumer? = null) : AbstractField<Boolean>(), IBooleanField {
private var remote: Boolean = false private var remote: Boolean = false
private var isRemoteSet = false private var isRemoteSet = false
private var clientValue: Boolean = false private var clientValue: Boolean = false
private var isClientValue = false private var isClientValue = false
private val subs = IBooleanSubscriptable.Impl()
init {
if (observer != null) {
subs.addListener(observer)
}
}
override fun addListener(listener: BooleanConsumer): ISubscriptable.L {
return subs.addListener(listener)
}
override val property = object : IBooleanProperty { override val property = object : IBooleanProperty {
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
@ -1436,7 +1578,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
val newValue = stream.readBoolean() val newValue = stream.readBoolean()
clientValue = newValue clientValue = newValue
isClientValue = true isClientValue = true
observer.accept(newValue) subs.accept(newValue)
} }
@Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property")) @Deprecated("Use type specific property", replaceWith = ReplaceWith("this.property"))
@ -1453,6 +1595,11 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
private val getter: () -> V private val getter: () -> V
private val setter: (V) -> Unit private val setter: (V) -> Unit
private var remote: V private var remote: V
private val subs = ISubscriptable.Impl<V>()
override fun addListener(listener: Consumer<V>): ISubscriptable.L {
return subs.addListener(listener)
}
override var value: V override var value: V
get() = getter.invoke() get() = getter.invoke()
@ -1499,6 +1646,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
override fun read(stream: DataInputStream) { override fun read(stream: DataInputStream) {
check(!isRemoved) { "Field was removed" } check(!isRemoved) { "Field was removed" }
this.value = codec.read(stream) this.value = codec.read(stream)
subs.accept(this.value)
} }
} }
@ -1506,7 +1654,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
private val codec: IStreamCodec<E>, private val codec: IStreamCodec<E>,
private val backingSet: MutableSet<E>, private val backingSet: MutableSet<E>,
private val callback: ((changes: Collection<SetChangeset<E>>) -> Unit)? = null, private val callback: ((changes: Collection<SetChangeset<E>>) -> Unit)? = null,
) : AbstractField<MutableSet<E>>() { ) : AbstractField<MutableSet<E>>(), ISubscriptable<MutableSet<E>> by ISubscriptable.empty() {
private var isRemote = false private var isRemote = false
private fun pushBacklog(element: E, action: (DataOutputStream) -> Unit) { private fun pushBacklog(element: E, action: (DataOutputStream) -> Unit) {
@ -1745,7 +1893,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
private val valueCodec: IStreamCodec<V>, private val valueCodec: IStreamCodec<V>,
private val backingMap: MutableMap<K, V>, private val backingMap: MutableMap<K, V>,
private val callback: ((changes: Collection<MapChangeset<K, V>>) -> Unit)? = null, private val callback: ((changes: Collection<MapChangeset<K, V>>) -> Unit)? = null,
) : AbstractField<MutableMap<K, V>>(), IField<MutableMap<K, V>> { ) : AbstractField<MutableMap<K, V>>(), IField<MutableMap<K, V>>, ISubscriptable<MutableMap<K, V>> by ISubscriptable.empty() {
private var sentAllValues = false private var sentAllValues = false
private var isRemote = false private var isRemote = false

View File

@ -1,6 +1,12 @@
package ru.dbotthepony.mc.otm.network.synchronizer package ru.dbotthepony.mc.otm.network.synchronizer
import ru.dbotthepony.mc.otm.core.FloatSupplier import ru.dbotthepony.mc.otm.core.FloatSupplier
import ru.dbotthepony.mc.otm.core.IBooleanSubscriptable
import ru.dbotthepony.mc.otm.core.IDoubleSubcripable
import ru.dbotthepony.mc.otm.core.IFloatSubcripable
import ru.dbotthepony.mc.otm.core.IIntSubcripable
import ru.dbotthepony.mc.otm.core.ILongSubcripable
import ru.dbotthepony.mc.otm.core.ISubscriptable
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.function.BooleanSupplier import java.util.function.BooleanSupplier
@ -11,7 +17,7 @@ import java.util.function.Supplier
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
sealed interface IField<V> : ReadOnlyProperty<Any?, V>, Supplier<V>, () -> V { sealed interface IField<V> : ReadOnlyProperty<Any?, V>, Supplier<V>, () -> V, ISubscriptable<V> {
fun observe(): Boolean fun observe(): Boolean
fun markDirty() fun markDirty()
fun markDirty(endpoint: FieldSynchronizer.Endpoint) fun markDirty(endpoint: FieldSynchronizer.Endpoint)
@ -40,7 +46,7 @@ interface IFloatProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Float operator fun getValue(thisRef: Any?, property: KProperty<*>): Float
} }
sealed interface IFloatField : IField<Float>, FloatSupplier { sealed interface IFloatField : IField<Float>, FloatSupplier, IFloatSubcripable {
val float: Float val float: Float
val property: IFloatProperty val property: IFloatProperty
@ -66,7 +72,7 @@ interface IDoubleProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Double operator fun getValue(thisRef: Any?, property: KProperty<*>): Double
} }
sealed interface IDoubleField : IField<Double>, DoubleSupplier { sealed interface IDoubleField : IField<Double>, DoubleSupplier, IDoubleSubcripable {
val double: Double val double: Double
val property: IDoubleProperty val property: IDoubleProperty
@ -92,7 +98,7 @@ interface IIntProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int operator fun getValue(thisRef: Any?, property: KProperty<*>): Int
} }
sealed interface IIntField : IField<Int>, IntSupplier { sealed interface IIntField : IField<Int>, IntSupplier, IIntSubcripable {
val int: Int val int: Int
val property: IIntProperty val property: IIntProperty
@ -118,7 +124,7 @@ interface ILongProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Long operator fun getValue(thisRef: Any?, property: KProperty<*>): Long
} }
sealed interface ILongField : IField<Long>, LongSupplier { sealed interface ILongField : IField<Long>, LongSupplier, ILongSubcripable {
val long: Long val long: Long
val property: ILongProperty val property: ILongProperty
@ -144,7 +150,7 @@ interface IBooleanProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean
} }
sealed interface IBooleanField : IField<Boolean>, BooleanSupplier { sealed interface IBooleanField : IField<Boolean>, BooleanSupplier, IBooleanSubscriptable {
val boolean: Boolean val boolean: Boolean
val property: IBooleanProperty val property: IBooleanProperty

View File

@ -3,12 +3,13 @@ package ru.dbotthepony.mc.otm.network.synchronizer
import it.unimi.dsi.fastutil.booleans.BooleanConsumer import it.unimi.dsi.fastutil.booleans.BooleanConsumer
import it.unimi.dsi.fastutil.floats.FloatConsumer import it.unimi.dsi.fastutil.floats.FloatConsumer
import ru.dbotthepony.mc.otm.core.GetterSetter import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.SentientGetterSetter
import java.util.function.DoubleConsumer import java.util.function.DoubleConsumer
import java.util.function.IntConsumer import java.util.function.IntConsumer
import java.util.function.LongConsumer import java.util.function.LongConsumer
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
sealed interface IMutableField<V> : IField<V>, GetterSetter<V> { sealed interface IMutableField<V> : IField<V>, SentientGetterSetter<V> {
override fun getValue(thisRef: Any?, property: KProperty<*>): V { override fun getValue(thisRef: Any?, property: KProperty<*>): V {
return this.value return this.value
} }

View File

@ -18,7 +18,7 @@ import net.minecraftforge.registries.ForgeRegistries
import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.capability.matter.matter import ru.dbotthepony.mc.otm.capability.matter.matter
import ru.dbotthepony.mc.otm.capability.matteryEnergy import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.core.util.CreativeMenuComparator import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.ifPresentK import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.registryName
@ -177,6 +177,9 @@ private fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) {
accept(MItems.PATTERN_DRIVE_CREATIVE) accept(MItems.PATTERN_DRIVE_CREATIVE)
accept(MItems.PATTERN_DRIVE_CREATIVE2) accept(MItems.PATTERN_DRIVE_CREATIVE2)
accept(MItems.PORTABLE_CONDENSATION_DRIVE)
accept(MItems.PORTABLE_DENSE_CONDENSATION_DRIVE)
fluids(MItems.FLUID_CAPSULE) fluids(MItems.FLUID_CAPSULE)
fluids(MItems.FLUID_TANK) fluids(MItems.FLUID_TANK)
@ -285,7 +288,7 @@ object MCreativeTabs {
} }
fun register(event: BuildCreativeModeTabContentsEvent) { fun register(event: BuildCreativeModeTabContentsEvent) {
CreativeMenuComparator.invalidate() CreativeMenuItemComparator.invalidate()
when (event.tab) { when (event.tab) {
MAIN -> addMainCreativeTabItems(event) MAIN -> addMainCreativeTabItems(event)

View File

@ -156,8 +156,8 @@ object MItems {
::MATTER_SCANNER, ::MATTER_PANEL, ::MATTER_REPLICATOR, ::MATTER_BOTTLER, ::ENERGY_COUNTER, ::CHEMICAL_GENERATOR, ::MATTER_SCANNER, ::MATTER_PANEL, ::MATTER_REPLICATOR, ::MATTER_BOTTLER, ::ENERGY_COUNTER, ::CHEMICAL_GENERATOR,
::MATTER_RECYCLER, ::PLATE_PRESS, ::TWIN_PLATE_PRESS, ::POWERED_FURNACE, ::POWERED_BLAST_FURNACE, ::MATTER_RECYCLER, ::PLATE_PRESS, ::TWIN_PLATE_PRESS, ::POWERED_FURNACE, ::POWERED_BLAST_FURNACE,
::POWERED_SMOKER, ::POWERED_SMOKER,
// ::STORAGE_BUS, ::STORAGE_IMPORTER, ::STORAGE_EXPORTER, ::DRIVE_VIEWER, ::STORAGE_BUS, ::STORAGE_IMPORTER, ::STORAGE_EXPORTER, ::DRIVE_VIEWER,
// ::DRIVE_RACK, ::ITEM_MONITOR, ::STORAGE_CABLE, ::STORAGE_POWER_SUPPLIER, ::DRIVE_RACK, ::ITEM_MONITOR, ::STORAGE_CABLE, ::STORAGE_POWER_SUPPLIER,
::ENERGY_SERVO, ::ENERGY_SERVO,
::PHANTOM_ATTRACTOR, ::GRAVITATION_STABILIZER, ::COBBLESTONE_GENERATOR, ::INFINITE_WATER_SOURCE, ::PHANTOM_ATTRACTOR, ::GRAVITATION_STABILIZER, ::COBBLESTONE_GENERATOR, ::INFINITE_WATER_SOURCE,
::ESSENCE_STORAGE, ::MATTER_RECONSTRUCTOR ::ESSENCE_STORAGE, ::MATTER_RECONSTRUCTOR

View File

@ -121,6 +121,10 @@ interface IStorageProvider<T : StorageStack<T>> : IStorageEventProducer<T> {
* @return copy of object, with amount of units actually extracted * @return copy of object, with amount of units actually extracted
*/ */
fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T
fun extractStack(id: T, amount: BigInteger, simulate: Boolean): T {
return extractStack(get(id) ?: return storageType.empty, amount, simulate)
}
} }
/** /**

View File

@ -1,7 +1,10 @@
package ru.dbotthepony.mc.otm.storage package ru.dbotthepony.mc.otm.storage
import net.minecraft.network.chat.Component
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.getValue
import ru.dbotthepony.mc.otm.core.lazy2
import ru.dbotthepony.mc.otm.core.math.toIntSafe import ru.dbotthepony.mc.otm.core.math.toIntSafe
import java.math.BigInteger import java.math.BigInteger
@ -43,6 +46,8 @@ class ItemStorageStack private constructor(private val stack: ItemStack, count:
override val isEmpty: Boolean = stack.isEmpty || super.isEmpty override val isEmpty: Boolean = stack.isEmpty || super.isEmpty
override val maxStackSize: BigInteger = BigInteger.valueOf(stack.maxStackSize.toLong()) override val maxStackSize: BigInteger = BigInteger.valueOf(stack.maxStackSize.toLong())
val hoverName: Component by lazy2({ toItemStack().hoverName }, { copy() })
fun toItemStack(count: Int = this.count.toIntSafe()): ItemStack { fun toItemStack(count: Int = this.count.toIntSafe()): ItemStack {
return stack.copyWithCount(count) return stack.copyWithCount(count)
} }

View File

@ -63,9 +63,19 @@ abstract class StorageStack<S : StorageStack<S>>(val count: BigInteger) {
fun read(buff: FriendlyByteBuf): T fun read(buff: FriendlyByteBuf): T
fun write(buff: FriendlyByteBuf, value: T) fun write(buff: FriendlyByteBuf, value: T)
fun energyPerUpkeep(stack: T): Decimal = Decimal.ZERO /**
* If there is not enough energy for operation, it is completely cancelled
*/
fun energyPerInsert(stack: T): Decimal = energyPerOperation(stack) fun energyPerInsert(stack: T): Decimal = energyPerOperation(stack)
/**
* If there is not enough energy for operation, it is completely cancelled
*/
fun energyPerExtract(stack: T): Decimal = energyPerOperation(stack) fun energyPerExtract(stack: T): Decimal = energyPerOperation(stack)
/**
* If there is not enough energy for operation, it is completely cancelled
*/
fun energyPerOperation(stack: T): Decimal fun energyPerOperation(stack: T): Decimal
} }
@ -113,7 +123,7 @@ abstract class StorageStack<S : StorageStack<S>>(val count: BigInteger) {
ItemStorageStack.unsafe(it.readItem(), it.readBigInteger()) ItemStorageStack.unsafe(it.readItem(), it.readBigInteger())
}, },
{ buff, v -> { buff, v ->
buff.writeItem(v.toItemStack()) buff.writeItem(v.toItemStack(1))
buff.writeBigInteger(v.count) buff.writeBigInteger(v.count)
} }
) )

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.mc.otm.storage
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import ru.dbotthepony.mc.otm.core.math.isPositive import ru.dbotthepony.mc.otm.core.math.isPositive
import ru.dbotthepony.mc.otm.core.math.isZero import ru.dbotthepony.mc.otm.core.math.isZero
import java.math.BigInteger import java.math.BigInteger
@ -41,23 +42,19 @@ class LocalTuple<T : StorageStack<T>>(override var stack: T, override val id: UU
} }
} }
open class VirtualComponent<T : StorageStack<T>>(override val storageType: StorageStack.Type<T>) : IVirtualStorageComponent<T> { class VirtualComponent<T : StorageStack<T>>(override val storageType: StorageStack.Type<T>) : IVirtualStorageComponent<T> {
// удаленный UUID -> Кортеж // удаленный UUID -> Кортеж
protected val remoteTuples = Object2ObjectOpenHashMap<UUID, RemoteTuple<T>>() private val remoteTuples = Object2ObjectOpenHashMap<UUID, RemoteTuple<T>>()
// локальный UUID -> Локальный кортеж // локальный UUID -> Локальный кортеж
protected val localTuples = Object2ObjectOpenHashMap<UUID, LocalTuple<T>>() private val localTuples = Object2ObjectOpenHashMap<UUID, LocalTuple<T>>()
// Стак -> Локальный кортеж стака // Стак -> Локальный кортеж стака
protected val item2tuple = Object2ObjectOpenCustomHashMap<T, LocalTuple<T>>(StorageStack.Companion) private val item2tuple = Object2ObjectOpenCustomHashMap<T, LocalTuple<T>>(StorageStack.Companion)
// ArrayList для скорости работы private val listeners = ObjectLinkedOpenHashSet<IStorageEventConsumer<T>>()
protected val listeners: MutableSet<IStorageEventConsumer<T>> = ObjectArraySet() private val children = ObjectLinkedOpenHashSet<IStorage<T>>()
protected val children: MutableSet<IStorage<T>> = ObjectArraySet() private val consumers = ObjectLinkedOpenHashSet<IStorageAcceptor<T>>()
protected val consumers: MutableSet<IStorageAcceptor<T>> = ObjectArraySet()
protected open fun onAdd(identity: IStorage<T>) {}
protected open fun onRemove(identity: IStorage<T>) {}
override fun add(identity: IStorage<T>) { override fun add(identity: IStorage<T>) {
require(identity.storageType == storageType) { "Attempt to add component of type ${identity.storageType} to virtual component of type $storageType" } require(identity.storageType == storageType) { "Attempt to add component of type ${identity.storageType} to virtual component of type $storageType" }
@ -76,8 +73,6 @@ open class VirtualComponent<T : StorageStack<T>>(override val storageType: Stora
if (identity is IStorageAcceptor<T>) { if (identity is IStorageAcceptor<T>) {
consumers.add(identity) consumers.add(identity)
} }
onAdd(identity)
} }
} }
@ -98,8 +93,6 @@ open class VirtualComponent<T : StorageStack<T>>(override val storageType: Stora
if (identity is IStorageAcceptor<T>) { if (identity is IStorageAcceptor<T>) {
consumers.remove(identity) consumers.remove(identity)
} }
onRemove(identity)
} }
} }
@ -231,4 +224,8 @@ open class VirtualComponent<T : StorageStack<T>>(override val storageType: Stora
return this.storageType.empty return this.storageType.empty
} }
override fun toString(): String {
return "VirtualComponent[$storageType; listeners: ${listeners.size}; size: ${localTuples.size}]"
}
} }

View File

@ -13,4 +13,16 @@ class PoweredComponent<T : StorageStack<T>>(
) : IStorageComponent<T>, IStorageProvider<T> by PoweredStorageProvider(parent, energy), IStorageAcceptor<T> by PoweredStorageAcceptor(parent, energy) { ) : IStorageComponent<T>, IStorageProvider<T> by PoweredStorageProvider(parent, energy), IStorageAcceptor<T> by PoweredStorageAcceptor(parent, energy) {
override val storageType: StorageStack.Type<T> override val storageType: StorageStack.Type<T>
get() = parent.storageType get() = parent.storageType
override fun equals(other: Any?): Boolean {
return other is PoweredComponent<*> && parent == other.parent
}
override fun hashCode(): Int {
return parent.hashCode()
}
override fun toString(): String {
return "PoweredComponent[$parent]"
}
} }

View File

@ -6,6 +6,18 @@ import ru.dbotthepony.mc.otm.storage.StorageStack
import java.util.function.Supplier import java.util.function.Supplier
class PoweredStorageAcceptor<T : StorageStack<T>>(val parent: IStorageAcceptor<T>, val energy: Supplier<IMatteryEnergyStorage>) : IStorageAcceptor<T> by parent { class PoweredStorageAcceptor<T : StorageStack<T>>(val parent: IStorageAcceptor<T>, val energy: Supplier<IMatteryEnergyStorage>) : IStorageAcceptor<T> by parent {
override fun equals(other: Any?): Boolean {
return other is PoweredStorageAcceptor<*> && parent == other.parent
}
override fun hashCode(): Int {
return parent.hashCode()
}
override fun toString(): String {
return "PoweredStorageAcceptor[$parent]"
}
override fun insertStack(stack: T, simulate: Boolean): T { override fun insertStack(stack: T, simulate: Boolean): T {
val leftover = parent.insertStack(stack, true) val leftover = parent.insertStack(stack, true)
if (leftover == stack) return stack if (leftover == stack) return stack

View File

@ -1,6 +1,10 @@
package ru.dbotthepony.mc.otm.storage.powered package ru.dbotthepony.mc.otm.storage.powered
import it.unimi.dsi.fastutil.HashCommon.mix
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer
import ru.dbotthepony.mc.otm.storage.IStorageProvider import ru.dbotthepony.mc.otm.storage.IStorageProvider
import ru.dbotthepony.mc.otm.storage.StorageStack import ru.dbotthepony.mc.otm.storage.StorageStack
import java.math.BigInteger import java.math.BigInteger
@ -8,8 +12,57 @@ import java.util.*
import java.util.function.Supplier import java.util.function.Supplier
class PoweredStorageProvider<T : StorageStack<T>>(val parent: IStorageProvider<T>, val energy: Supplier<IMatteryEnergyStorage>) : IStorageProvider<T> by parent { class PoweredStorageProvider<T : StorageStack<T>>(val parent: IStorageProvider<T>, val energy: Supplier<IMatteryEnergyStorage>) : IStorageProvider<T> by parent {
override fun equals(other: Any?): Boolean {
return other is PoweredStorageProvider<*> && other.parent == parent
}
override fun hashCode(): Int {
return parent.hashCode()
}
override fun toString(): String {
return "PoweredStorageProvider[$parent]"
}
private class Proxy<T : StorageStack<T>>(private val parent: IStorageEventConsumer<T>, private val powered: PoweredStorageProvider<T>) : IStorageEventConsumer<T> {
override val storageType: StorageStack.Type<T>
get() = parent.storageType
override fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider<T>) {
parent.onStackAdded(stack, id, powered)
}
override fun onStackChanged(stack: T, id: UUID) {
parent.onStackChanged(stack, id)
}
override fun onStackRemoved(id: UUID) {
parent.onStackRemoved(id)
}
override fun toString(): String {
return "PoweredStorageProvider.Proxy[$parent with $powered]"
}
override fun equals(other: Any?): Boolean {
return other is Proxy<*> && parent == other.parent && powered == other.powered
}
override fun hashCode(): Int {
return mix(parent.hashCode() * 31 + powered.hashCode())
}
}
override fun addListener(listener: IStorageEventConsumer<T>): Boolean {
return parent.addListener(Proxy(listener, this))
}
override fun removeListener(listener: IStorageEventConsumer<T>): Boolean {
return parent.removeListener(Proxy(listener, this))
}
override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T { override fun extractStack(id: UUID, amount: BigInteger, simulate: Boolean): T {
val extracted = parent.extractStack(id, amount, simulate) val extracted = parent.extractStack(id, amount, true)
if (extracted.isEmpty) return extracted if (extracted.isEmpty) return extracted
val energy = energy.get() val energy = energy.get()

View File

@ -16,6 +16,18 @@ class PoweredVirtualComponent<T : StorageStack<T>>(
) : IVirtualStorageComponent<T>, IStorageComponent<T> by PoweredComponent(parent, energy) { ) : IVirtualStorageComponent<T>, IStorageComponent<T> by PoweredComponent(parent, energy) {
constructor(type: StorageStack.Type<T>, energy: Supplier<IMatteryEnergyStorage>) : this(VirtualComponent(type), energy) constructor(type: StorageStack.Type<T>, energy: Supplier<IMatteryEnergyStorage>) : this(VirtualComponent(type), energy)
override fun equals(other: Any?): Boolean {
return other is PoweredVirtualComponent<*> && parent == other.parent
}
override fun hashCode(): Int {
return parent.hashCode()
}
override fun toString(): String {
return "PoweredVirtualComponent[$parent]"
}
override fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider<T>) = parent.onStackAdded(stack, id, provider) override fun onStackAdded(stack: T, id: UUID, provider: IStorageProvider<T>) = parent.onStackAdded(stack, id, provider)
override fun onStackChanged(stack: T, id: UUID) = parent.onStackChanged(stack, id) override fun onStackChanged(stack: T, id: UUID) = parent.onStackChanged(stack, id)
override fun onStackRemoved(id: UUID) = parent.onStackRemoved(id) override fun onStackRemoved(id: UUID) = parent.onStackRemoved(id)