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

View File

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

View File

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

View File

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

View File

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

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.MatteryContainer
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.orNull
import ru.dbotthepony.mc.otm.core.util.FluidStackValueCodec

View File

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

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

View File

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

View File

@ -1,10 +1,10 @@
package ru.dbotthepony.mc.otm.block.entity.storage
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import net.minecraft.core.BlockPos
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
@ -19,19 +19,18 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.common.ForgeHooks
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.network.NetworkEvent
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.block.entity.MatteryPoweredBlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.graph.storage.StorageNode
import ru.dbotthepony.mc.otm.graph.storage.StorageGraph
import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.container.set
import ru.dbotthepony.mc.otm.core.nbt.mapString
@ -41,144 +40,143 @@ import ru.dbotthepony.mc.otm.storage.*
import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent
import java.math.BigInteger
import java.util.*
import java.util.function.Supplier
import kotlin.collections.HashMap
import ru.dbotthepony.mc.otm.client.render.Widgets8
import ru.dbotthepony.mc.otm.container.CombinedContainer
import ru.dbotthepony.mc.otm.container.addItem
import ru.dbotthepony.mc.otm.container.fullIterator
import ru.dbotthepony.mc.otm.container.iterator
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.collect.toList
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
class ItemMonitorPlayerSettings : INBTSerializable<CompoundTag>, MatteryPacket {
enum class IngredientPriority(val component: Component) {
// Refill everything from system
SYSTEM(TranslatableComponent("otm.gui.item_monitor.refill_source.system")),
interface IItemMonitorPlayerSettings {
var ingredientPriority: ItemMonitorPlayerSettings.IngredientPriority
var resultTarget: ItemMonitorPlayerSettings.ResultTarget
var craftingAmount: ItemMonitorPlayerSettings.Amount
var sorting: ItemStorageStackSorter
var ascendingSort: Boolean
}
// Refill everything from player's inventory
INVENTORY(TranslatableComponent("otm.gui.item_monitor.refill_source.inventory")),
private fun takeOne(inventory: CombinedContainer?, item: ItemStack): Boolean {
val iterator = inventory?.optimizedIterator() ?: return false
// 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")),
for (stack in iterator) {
if (stack.equals(item, false)) {
stack.shrink(1)
inventory.setChanged()
return true
}
}
enum class ResultTarget(val component: Component) {
return false
}
private fun takeOne(id: UUID?, view: IStorageProvider<ItemStorageStack>?): Boolean {
return view?.extractStack(id ?: return false, BigInteger.ONE, false)?.isNotEmpty == true
}
class ItemMonitorPlayerSettings : INBTSerializable<CompoundTag>, IItemMonitorPlayerSettings {
interface Setting {
val component: Component
val icon: IGUIRenderable
val winding: UVWindingOrder
}
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")),
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")),
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")),
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(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"))
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
}
var ingredientPriority = IngredientPriority.SYSTEM
var resultTarget = ResultTarget.MIXED
var craftingAmount = Amount.STACK
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[INGREDIENT_PRIORITY_KEY] = ingredientPriority.name
it[RESULT_TARGET_KEY] = resultTarget.name
it[QUICK_CRAFT_AMOUNT_KEY] = craftingAmount.name
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(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)
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")
}
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 {
for (i in 0 until inventory.containerSize) {
if (!inventory[i].isEmpty && ItemStack.isSameItemSameTags(inventory[i], item)) {
inventory.removeItem(i, 1)
return true
}
}
return false
}
private fun takeOne(id: UUID?, view: IStorageProvider<ItemStorageStack>): Boolean {
val extracted = view.extractStack(id ?: return false, BigInteger.ONE, false)
if (!extracted.isEmpty) {
return true
}
return false
}
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 {
exposeEnergyGlobally(energy)
savetable(::energy, ENERGY_KEY)
}
@ -203,40 +201,42 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
}
private val settings = HashMap<UUID, ItemMonitorPlayerSettings>()
private val craftingAmount = Object2ObjectArrayMap<Player, Int>()
private val craftingAmount = Object2IntOpenHashMap<Player>()
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]
// a lot of code is hardcoded to take CraftingContainer as it's input
// hence we are forced to work around this by providing proxy container
val craftingGrid = object : MatteryContainer(::setChangedLight, 3 * 3) {
val craftingGrid = object : MatteryContainer(3 * 3) {
override fun setChanged(slot: Int, new: ItemStack, old: ItemStack) {
super.setChanged(slot, new, old)
craftingGridDummy[slot] = new
setChangedLight()
craftingGridVanilla[slot] = new
if (!inProcessOfCraft)
scanCraftingGrid()
if (!inProcessOfCraft) {
scanCraftingGrid(false)
}
}
}.also(::addDroppableContainer)
private var inProcessOfCraft = false
private val craftingGridVanilla = TransientCraftingContainer(object : AbstractContainerMenu(null, Int.MIN_VALUE) {
override fun stillValid(p_38874_: Player) = true
override fun quickMoveStack(p_38941_: Player, p_38942_: Int) = ItemStack.EMPTY
}, 3, 3)
private fun scanCraftingGrid() {
val server = level?.server ?: return
val oldRecipe = craftingRecipe
private fun scanCraftingGrid(justCheckForRecipeChange: Boolean): Boolean {
val level = level ?: return false
val server = level.server ?: return false
if (oldRecipe != null && oldRecipe.matches(craftingGridDummy, level!!)) {
return
}
var craftingRecipe = craftingRecipe
if (craftingRecipe != null && craftingRecipe.matches(craftingGridVanilla, level)) return true
if (justCheckForRecipeChange) return false
craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGridDummy, level!!).orElse(null)
val craftingRecipe = craftingRecipe
if (oldRecipe != craftingRecipe) {
craftingRecipe = server.recipeManager.getRecipeFor(RecipeType.CRAFTING, craftingGridVanilla, level).orElse(null)
Arrays.fill(craftingGridTuples, null)
val poweredView = poweredView
if (craftingRecipe != null && poweredView != null) {
@ -248,34 +248,27 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
}
}
}
}
}
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
this.craftingRecipe = craftingRecipe
return false
}
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>
get() = StorageStack.ITEMS
override fun onStackAdded(stack: ItemStorageStack, id: UUID, provider: IStorageProvider<ItemStorageStack>) {
// no op
}
override fun onStackChanged(stack: ItemStorageStack, id: UUID) {
// no op
if (craftingRecipe != null) {
for (i in craftingGridTuples.indices) {
val item = craftingGrid[i]
if (!item.isEmpty && stack.equalsWithoutCount(ItemStorageStack.unsafe(item))) {
craftingGridTuples[i] = id
}
}
}
}
override fun onStackChanged(stack: ItemStorageStack, id: UUID) {}
override fun onStackRemoved(id: UUID) {
for (i in craftingGridTuples.indices) {
if (craftingGridTuples[i] == id) {
@ -300,137 +293,112 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
override fun getContainerSize() = 1
override fun isEmpty() = craftingRecipe == null
override fun stillValid(p_18946_: Player) = true
override fun stillValid(player: Player) = true
override fun getItem(p_18941_: Int): ItemStack {
require(p_18941_ == 0) { "Invalid slot ID: $p_18941_" }
override fun getItem(slot: Int): ItemStack {
require(slot == 0) { "Invalid slot: $slot" }
return craftingRecipe?.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY)?.copy() ?: ItemStack.EMPTY
}
override fun removeItem(index: Int, amount: Int): ItemStack {
require(index == 0) { "Invalid index $index" }
val craftingRecipe = craftingRecipe
val craftingPlayer = craftingPlayer
val level = level ?: return ItemStack.EMPTY
val craftingRecipe = craftingRecipe ?: return ItemStack.EMPTY
val craftingPlayer = craftingPlayer ?: return ItemStack.EMPTY
if (craftingRecipe == null || craftingPlayer == null || craftingRecipe.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY).count != amount) {
try {
ForgeHooks.setCraftingPlayer(craftingPlayer)
if (craftingRecipe.getResultItem(level.registryAccess()).count != amount) {
return ItemStack.EMPTY
}
} finally {
ForgeHooks.setCraftingPlayer(null)
}
var crafts = craftingAmount[craftingPlayer] ?: 0
crafts++
craftingAmount[craftingPlayer] = crafts
craftingAmount[craftingPlayer] = craftingAmount.getInt(craftingPlayer) + 1
lastCraftingRecipe[craftingPlayer] = craftingRecipe
tickList.namedTimer(craftingPlayer, 4) {
craftingAmount.removeInt(craftingPlayer)
lastCraftingRecipe.remove(craftingPlayer)
}
val settings = getSettings(craftingPlayer)
inProcessOfCraft = true
try {
ForgeHooks.setCraftingPlayer(craftingPlayer)
val remainingItems: NonNullList<ItemStack> = craftingRecipe.getRemainingItems(craftingGridDummy)
val residue: NonNullList<ItemStack>
val result: ItemStack
try {
residue = craftingRecipe.getRemainingItems(craftingGridVanilla)
result = craftingRecipe.getResultItem(level.registryAccess())
} finally {
ForgeHooks.setCraftingPlayer(null)
}
check(remainingItems.size == craftingGrid.containerSize) { "${remainingItems.size} != ${craftingGrid.containerSize} !!!" }
check(residue.size == craftingGrid.containerSize) { "Container and residue list sizes mismatch: ${residue.size} != ${craftingGrid.containerSize}" }
val combinedInventory = craftingPlayer.matteryPlayer?.combinedInventory
val copy = craftingGrid.fullIterator().map { it.copy() }.toList()
// удаляем по одному предмету из сетки крафта
for (slot in 0 until craftingGrid.containerSize) {
val oldItem = craftingGrid[slot].let { if (!it.isEmpty || it.count < 2) it.copy() else it }
if (!craftingGrid[slot].isEmpty) {
craftingGrid.removeItem(slot, 1)
}
var newItem = craftingGrid[slot]
if (newItem.isEmpty) {
when (settings.ingredientPriority) {
ItemMonitorPlayerSettings.IngredientPriority.SYSTEM -> {
if (poweredView != null && takeOne(craftingGridTuples[slot], poweredView!!)) {
newItem = oldItem
craftingGrid[slot] = newItem
if (residue[slot].isNotEmpty) {
craftingGrid[slot] = residue[slot]
}
}
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 (!remainder.isEmpty) {
when (settings.resultTarget) {
ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM, ItemMonitorPlayerSettings.ResultTarget.MIXED -> {
val remaining = poweredView?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder
if (!remaining.isEmpty) {
if (newItem.isEmpty) {
craftingGrid[slot] = remaining
} else if (ItemStack.isSameItemSameTags(newItem, remaining)) {
newItem.grow(remaining.count)
craftingGrid.setChanged(slot)
} else if (!craftingPlayer.inventory.add(remaining)) {
craftingPlayer.drop(remaining, false)
if (!scanCraftingGrid(true)) {
// заполняем опустевшие слоты
for (slot in 0 until craftingGrid.containerSize) {
if (
craftingGrid[slot].isEmpty &&
copy[slot].isNotEmpty &&
settings.ingredientPriority.takeOne(copy[slot], poweredView, combinedInventory, craftingGridTuples[slot])
) {
craftingGrid[slot] = copy[slot].copyWithCount(1)
}
}
}
ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY -> {
if (!craftingPlayer.inventory.add(remainder)) {
val remaining = poweredView?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder
if (!scanCraftingGrid(true)) {
// избавляемся от остатков, если они мешают
for (slot in 0 until craftingGrid.containerSize) {
if (residue[slot].isNotEmpty) {
var remaining = residue[slot]
if (!remaining.isEmpty) {
if (newItem.isEmpty) {
craftingGrid[slot] = remaining
} else if (ItemStack.isSameItemSameTags(newItem, remaining)) {
newItem.grow(remaining.count)
craftingGrid.setChanged(slot)
} else {
craftingPlayer.drop(remaining, false)
}
}
}
if (settings.resultTarget != ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY) {
remaining = poweredView?.insertStack(ItemStorageStack(remaining), false)?.toItemStack() ?: remaining
}
remaining = combinedInventory?.addItem(remaining, false) ?: remaining
if (remaining.isNotEmpty) {
craftingPlayer.spawnAtLocation(remaining)
}
if (copy[slot].isNotEmpty && settings.ingredientPriority.takeOne(copy[slot], poweredView, combinedInventory, craftingGridTuples[slot])) {
craftingGrid[slot] = copy[slot].copyWithCount(1)
}
}
}
}
return result.copy()
} finally {
inProcessOfCraft = false
scanCraftingGrid()
scanCraftingGrid(false)
}
}
return craftingRecipe.getResultItem(level?.registryAccess() ?: return ItemStack.EMPTY).copy()
}
override fun removeItemNoUpdate(p_18951_: Int): ItemStack {
override fun removeItemNoUpdate(slot: Int): ItemStack {
throw UnsupportedOperationException()
}
@ -464,11 +432,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
val settings = nbt.getCompound("player_settings")
for (key in settings.allKeys) {
val uuid = UUID.fromString(key)
val deserialized = ItemMonitorPlayerSettings()
deserialized.deserializeNBT(settings.getCompound(key))
check(this.settings.put(uuid, deserialized) == null) { "Duplicate UUID??? $uuid" }
check(this.settings.put(UUID.fromString(key), ItemMonitorPlayerSettings().also { it.deserializeNBT(settings.getCompound(key)) }) == null)
}
craftingGrid.deserializeNBT(nbt["crafting_grid"])
@ -480,14 +444,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
override fun tick() {
super.tick()
cell.tickEnergyDemanding()
if (craftingAmount.size != 0)
craftingAmount.clear()
if (lastCraftingRecipe.size != 0)
lastCraftingRecipe.clear()
}
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
@ -497,6 +454,7 @@ class ItemMonitorBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matte
override fun setLevel(level: Level) {
super.setLevel(level)
cell.discover(this)
scanCraftingGrid(false)
}
override fun setRemoved() {

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

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

View File

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

View File

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

View File

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

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

View File

@ -54,27 +54,10 @@ class MatterPanelScreen(
val controls = DeviceControls(this, frame)
LargeBooleanRectangleButtonPanel(
this,
controls,
prop = menu.isAscendingGS,
skinElementActive = Widgets18.ARROW_UP,
skinElementInactive = Widgets18.ARROW_DOWN,
tooltipActive = TranslatableComponent("otm.gui.sorting.ascending"),
tooltipInactive = TranslatableComponent("otm.gui.sorting.descending"),
).also {
controls.addButton(it)
controls.sortingButtons(menu.isAscendingGS, menu.sortingGS, ItemSorter.DEFAULT) {
for (v in ItemSorter.entries) {
add(v, skinElement = v.icon, tooltip = v.title)
}
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, {
@ -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()
if (isPatternView) {
@ -326,7 +309,7 @@ class MatterPanelScreen(
return pattern.stack()
}
override fun getItemStackTooltip(stack: ItemStack): List<Component> {
override fun getItemStackTooltip(stack: ItemStack): MutableList<Component> {
return super.getItemStackTooltip(stack).toMutableList().also {
it.add(TranslatableComponent(
"otm.item.pattern.research",

View File

@ -31,8 +31,26 @@ open class FramePanel<out S : Screen>(
var onOpen: Runnable? = null,
var onClose: Runnable? = null,
var activeIcon: IGUIRenderable? = null,
var inactiveIcon: IGUIRenderable? = null,
var inactiveIcon: IGUIRenderable? = activeIcon,
) : 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()
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.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.util.ItemSorter
import ru.dbotthepony.mc.otm.core.value
import ru.dbotthepony.mc.otm.menu.UpgradeSlots
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
@ -315,7 +316,6 @@ class DeviceControls<out S : MatteryScreen<*>>(
val upgradesButton: LargeRectangleButtonPanel<S>?
private var upgradeWindow: FramePanel<S>? = null
private var nextY = 0f
fun <P : EditablePanel<@UnsafeVariance S>> addButton(button: P): P {
@ -328,6 +328,40 @@ class DeviceControls<out S : MatteryScreen<*>>(
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 {
for (button in extra) {
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)
}

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.EditablePanel
open class GridPanel<out S : Screen> @JvmOverloads constructor(
open class GridPanel<out S : Screen>(
screen: S,
parent: EditablePanel<*>?,
x: Float = 0f,
y: Float = 0f,
width: Float,
height: Float,
protected var columns: Int,
protected var rows: Int
width: Float = 0f,
height: Float = 0f,
columns: Int,
rows: Int
) : EditablePanel<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() {
var currentX = 0f
var currentY = 0f

View File

@ -1,23 +1,26 @@
package ru.dbotthepony.mc.otm.client.screen.storage
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import ru.dbotthepony.mc.otm.client.render.ItemStackIcon
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.client.screen.panels.*
import ru.dbotthepony.mc.otm.client.screen.panels.button.CheckBoxLabelInputPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FilterSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.client.screen.widget.PowerGaugePanel
import ru.dbotthepony.mc.otm.core.math.integerDivisionDown
import ru.dbotthepony.mc.otm.client.screen.widget.WideProfiledPowerGaugePanel
import ru.dbotthepony.mc.otm.core.asGetterSetter
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem
import ru.dbotthepony.mc.otm.menu.storage.DriveViewerMenu
import ru.dbotthepony.mc.otm.registry.MItems
import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak
@MouseTweaksDisableWheelTweak
@ -25,106 +28,58 @@ class DriveViewerScreen(menu: DriveViewerMenu, inventory: Inventory, title: Comp
MatteryScreen<DriveViewerMenu>(menu, inventory, title) {
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.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<*>>()
frame.Tab(onOpen = {
for (panel in views) {
panel.visible = true
}
}, onClose = {
for (panel in views) {
panel.visible = false
view.add(EditablePanel(this, frame, width = AbstractSlotPanel.SIZE).also {
it.dock = Dock.LEFT
WideProfiledPowerGaugePanel(this, it, menu.profiledEnergy).also {
it.dock = Dock.TOP
it.dockBottom = 6f
}
BatterySlotPanel(this, it, menu.batterySlot).dock = Dock.TOP
SlotPanel(this, it, menu.driveSlot).dock = Dock.TOP
})
frame.Tab(onOpen = {
for (panel in settings) {
panel.visible = true
}
}, onClose = {
for (panel in settings) {
panel.visible = false
}
view.add(NetworkedItemGridPanel(this, frame, menu.networkedItemView).also {
it.dock = Dock.FILL
it.setDockMargin(4f, 0f, 0f, 0f)
})
views.add(PowerGaugePanel(this, frame, menu.energyWidget, 8f, 16f))
views.add(BatterySlotPanel(this, frame, menu.batterySlot, 8f, 67f))
views.add(SlotPanel(this, frame, menu.driveSlot, 8f, 85f))
val grid = GridPanel(this, frame, 28f, 16f, GRID_WIDTH * 18f, GRID_HEIGHT * 18f, GRID_WIDTH, GRID_HEIGHT)
val scrollBar = DiscreteScrollBarPanel(this, frame, { integerDivisionDown(menu.networkedItemView.itemCount, GRID_WIDTH) }, { _, _, _ -> }, 192f, 14f, 92f)
views.add(grid)
views.add(scrollBar)
for (i in 0 until GRID_WIDTH * GRID_HEIGHT) {
object : AbstractSlotPanel<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)
val filterGrid = GridPanel(this, frame, width = AbstractSlotPanel.SIZE * 3f, height = AbstractSlotPanel.SIZE * 4f, rows = 3, columns = 4)
filterGrid.dock = Dock.FILL
filterGrid.dockResize = DockResizeMode.NONE
filterGrid.dockTop = 20f
settings.add(filterGrid)
for (i in 0 until PortableCondensationDriveItem.MAX_FILTERS) {
FilterSlotPanel(this, grid_filter, menu.driveFilterSlots[i], 0f, 0f)
FilterSlotPanel(this, filterGrid, menu.driveFilterSlots[i], 0f, 0f)
}
CheckBoxLabelInputPanel(this, dock_left, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) }
CheckBoxLabelInputPanel(this, dock_left, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) }
CheckBoxLabelInputPanel(this, dock_left, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt"), width = 90f, height = 20f).also { it.setDockMargin(0f, 4f, 0f, 0f) }
settings.add(EditablePanel(this, frame, width = 90f).also {
it.dock = Dock.LEFT
CheckBoxLabelInputPanel(this, it, menu.isWhitelist, TranslatableComponent("otm.gui.filter.is_whitelist")).also { it.dockTop = 20f; it.dock = Dock.TOP }
CheckBoxLabelInputPanel(this, it, menu.matchTag, TranslatableComponent("otm.gui.filter.match_tag")).also { it.dockTop = 4f; it.dock = Dock.TOP }
CheckBoxLabelInputPanel(this, it, menu.matchNBT, TranslatableComponent("otm.gui.filter.match_nbt")).also { it.dockTop = 4f; it.dock = Dock.TOP }
})
for (panel in settings) {
panel.visible = false
}
frame.Tab(view, activeIcon = ItemStackIcon(ItemStack(MItems.PORTABLE_CONDENSATION_DRIVE)))
frame.Tab(settings, activeIcon = ItemStackIcon(ItemStack(Items.HOPPER)))
return frame
}
companion object {
const val FRAME_WIDTH = 210f
const val FRAME_HEIGHT = 110f
const val GRID_WIDTH = 9
const val GRID_HEIGHT = 5
}
}

View File

@ -1,23 +1,21 @@
package ru.dbotthepony.mc.otm.client.screen.storage
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import org.lwjgl.opengl.GL11
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.client.render.Widgets8
import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
import ru.dbotthepony.mc.otm.client.screen.panels.DockResizeMode
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
import ru.dbotthepony.mc.otm.client.screen.panels.NetworkedItemGridPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.DeviceControls
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeBooleanRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeEnumRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.button.SmallEnumRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel
@ -27,12 +25,10 @@ import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.client.screen.widget.ProgressGaugePanel
import ru.dbotthepony.mc.otm.client.screen.widget.WidePowerGaugePanel
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.asGetterSetter
import ru.dbotthepony.mc.otm.core.math.integerDivisionDown
import ru.dbotthepony.mc.otm.core.util.formatReadableNumber
import ru.dbotthepony.mc.otm.core.util.formatSiComponent
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.menu.storage.ItemMonitorMenu
import ru.dbotthepony.mc.otm.storage.StorageStack
import yalter.mousetweaks.api.MouseTweaksDisableWheelTweak
@MouseTweaksDisableWheelTweak
@ -57,70 +53,16 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
frame.height = topPanel.height + bottomPanel.height + frame.dockPadding.top + frame.dockPadding.bottom + 3f
frame.width = 178f + frame.dockPadding.left + frame.dockPadding.right
val viewScrollBar = DiscreteScrollBarPanel(this, topPanel,
{ integerDivisionDown(menu.networkedItemView.itemCount, ITEM_GRID_WIDTH) },
{ _, _, _ -> },
28f + ITEM_GRID_WIDTH * 18f + 2f, 16f, ITEM_GRID_HEIGHT * 18f)
val controls = DeviceControls(this, frame)
viewScrollBar.dock = Dock.RIGHT
viewScrollBar.setDockMargin(left = 2f)
val gridPanel = GridPanel(this, topPanel, width = ITEM_GRID_WIDTH * 18f, height = ITEM_GRID_HEIGHT * 18f, columns = ITEM_GRID_WIDTH, rows = ITEM_GRID_HEIGHT)
gridPanel.dock = Dock.FILL
for (i in 0 until ITEM_GRID_WIDTH * ITEM_GRID_HEIGHT) {
object : AbstractSlotPanel<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()
controls.sortingButtons(menu.settings::ascendingSort.asGetterSetter(), menu.settings::sorting.asGetterSetter(), ItemStorageStackSorter.DEFAULT) {
for (v in ItemStorageStackSorter.entries) {
add(v, skinElement = v.icon, tooltip = v.title)
}
}
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)
craftingGrid.dock = Dock.LEFT
@ -142,19 +84,19 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
val arrowLine = EditablePanel(this, arrowAndButtons, y = 38f, height = 8f, width = arrowAndButtons.width)
val refillPriority = SmallEnumRectangleButtonPanel(this, arrowLine,
SmallEnumRectangleButtonPanel(this, arrowLine,
enum = ItemMonitorPlayerSettings.IngredientPriority::class.java,
prop = menu.settings::ingredientPriority.asGetterSetter(watch = { _, _ -> menu.sendSettingsToServer() }),
prop = menu.settings::ingredientPriority.asGetterSetter(),
defaultValue = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM)
.also {
it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.refill_source.desc"))
refillPriority.tooltips.add(TranslatableComponent("otm.gui.item_monitor.refill_source.desc"))
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.SYSTEM, tooltip = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM.component, skinElement = Widgets8.WHITE_ARROW_DOWN, winding = UVWindingOrder.FLIP)
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.INVENTORY, tooltip = ItemMonitorPlayerSettings.IngredientPriority.INVENTORY.component, skinElement = Widgets8.WHITE_ARROW_DOWN)
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST, tooltip = ItemMonitorPlayerSettings.IngredientPriority.INVENTORY_FIRST.component, skinElement = Widgets8.ARROW_SIDEWAYS, winding = UVWindingOrder.FLIP)
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST, tooltip = ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST.component, skinElement = Widgets8.ARROW_SIDEWAYS)
refillPriority.add(ItemMonitorPlayerSettings.IngredientPriority.DO_NOT, tooltip = ItemMonitorPlayerSettings.IngredientPriority.DO_NOT.component, skinElement = Widgets8.MINUS)
for (setting in ItemMonitorPlayerSettings.IngredientPriority.entries) {
it.add(setting, setting.icon, setting.component, setting.winding)
}
refillPriority.dock = Dock.LEFT
it.dock = Dock.LEFT
}
val resultAndButtons = EditablePanel(this, bottomPanel, width = 18f)
@ -163,25 +105,29 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
SlotPanel(this, resultAndButtons, menu.craftingResult, y = 18f)
val resultTarget = SmallEnumRectangleButtonPanel(this, resultAndButtons, y = 38f,
SmallEnumRectangleButtonPanel(this, resultAndButtons, y = 38f,
enum = ItemMonitorPlayerSettings.ResultTarget::class.java,
prop = menu.settings::resultTarget.asGetterSetter(watch = { _, _ -> menu.sendSettingsToServer() }),
prop = menu.settings::resultTarget.asGetterSetter(),
defaultValue = ItemMonitorPlayerSettings.ResultTarget.MIXED)
.also {
it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.result_target.desc"))
resultTarget.tooltips.add(TranslatableComponent("otm.gui.item_monitor.result_target.desc"))
resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.MIXED, tooltip = ItemMonitorPlayerSettings.ResultTarget.MIXED.component, skinElement = Widgets8.ARROW_SIDEWAYS)
resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY, tooltip = ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY.component, skinElement = Widgets8.ARROW_PAINTED_UP, winding = UVWindingOrder.FLIP)
resultTarget.add(ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM, tooltip = ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM.component, skinElement = Widgets8.ARROW_PAINTED_UP)
for (setting in ItemMonitorPlayerSettings.ResultTarget.entries) {
it.add(setting, setting.icon, setting.component, setting.winding)
}
}
val craftingAmount = SmallEnumRectangleButtonPanel(this, resultAndButtons, x = 10f, y = 38f,
SmallEnumRectangleButtonPanel(this, resultAndButtons, x = 10f, y = 38f,
enum = ItemMonitorPlayerSettings.Amount::class.java,
prop = menu.settings::craftingAmount.asGetterSetter(watch = { _, _ -> menu.sendSettingsToServer() }),
prop = menu.settings::craftingAmount.asGetterSetter(),
defaultValue = ItemMonitorPlayerSettings.Amount.STACK)
.also {
it.tooltips.add(TranslatableComponent("otm.gui.item_monitor.amount.desc"))
craftingAmount.tooltips.add(TranslatableComponent("otm.gui.item_monitor.amount.desc"))
craftingAmount.add(ItemMonitorPlayerSettings.Amount.ONE, tooltip = ItemMonitorPlayerSettings.Amount.ONE.component, skinElement = Widgets8.ONE)
craftingAmount.add(ItemMonitorPlayerSettings.Amount.STACK, tooltip = ItemMonitorPlayerSettings.Amount.STACK.component, skinElement = Widgets8.S)
craftingAmount.add(ItemMonitorPlayerSettings.Amount.FULL, tooltip = ItemMonitorPlayerSettings.Amount.FULL.component, skinElement = Widgets8.F)
for (setting in ItemMonitorPlayerSettings.Amount.entries) {
it.add(setting, setting.icon, setting.component, setting.winding)
}
}
val craftingHistory = GridPanel(this, bottomPanel, width = 3 * 18f, height = 3 * 18f, columns = 3, rows = 3)
craftingHistory.dock = Dock.LEFT
@ -227,7 +173,5 @@ class ItemMonitorScreen(menu: ItemMonitorMenu, inventory: Inventory, title: Comp
companion object {
const val ITEM_GRID_WIDTH = 9
const val ITEM_GRID_HEIGHT = 5
const val FONT_SCALE = 0.6f
}
}

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_INTERFACES = conciseValues("STORAGE_INTERFACES", Decimal(10_000), Decimal(100))
val ITEM_MONITOR = conciseValues(MNames.ITEM_MONITOR, Decimal(10_000), Decimal(100))
val DRIVE_VIEWER = conciseValues(MNames.DRIVE_VIEWER, Decimal(10_000), Decimal(100))
val DRIVE_RACK = conciseValues(MNames.DRIVE_RACK, Decimal(10_000), Decimal(100))
val STORAGE_INTERFACES = conciseValues("STORAGE_INTERFACES", Decimal(10_000), Decimal(10_000))
val ITEM_MONITOR = conciseValues(MNames.ITEM_MONITOR, Decimal(10_000), Decimal(10_000))
val DRIVE_VIEWER = conciseValues(MNames.DRIVE_VIEWER, Decimal(10_000), Decimal(10_000))
val DRIVE_RACK = conciseValues(MNames.DRIVE_RACK, Decimal(10_000), Decimal(10_000))
object AndroidCharger {
val RADIUS_WIDTH: Double by builder

View File

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

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.container
import it.unimi.dsi.fastutil.objects.ObjectIterators
import net.minecraft.world.Container
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.core.isNotEmpty
@ -50,3 +51,32 @@ fun Container.iterator(): IContainerIterator {
ContainerIterator(this)
}
}
class FullContainerIterator(val container: Container, initialPos: Int = 0) : MutableIterator<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.UUID
import java.util.function.Consumer
import java.util.function.Supplier
import java.util.stream.Stream
import java.util.stream.StreamSupport
import kotlin.reflect.KProperty
@ -367,12 +368,20 @@ fun <T> Comparator<in T>.nullsLast(): Comparator<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]
*
* 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(fromIndex >= 0) { "Invalid from index: $fromIndex" }
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
*/
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)
}
@ -433,3 +442,8 @@ fun <E> MutableList<E>.addSorted(element: E, comparator: Comparator<E>) {
fun <E : Comparable<E>> MutableList<E>.addSorted(element: E) {
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> {
return object : GetterSetter<V> {
fun <V> box(value: V): SentientGetterSetter<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
override fun get(): V {
@ -103,12 +109,15 @@ interface GetterSetter<V> : Supplier<V>, Consumer<V>, ReadWriteProperty<Any?, V>
override fun accept(t: V) {
this.value = t
subs.accept(t)
}
}
}
}
}
interface SentientGetterSetter<V> : GetterSetter<V>, ISubscriptable<V>
operator fun <T> Supplier<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
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() {
throw UnsupportedOperationException()
(this@mapToDouble as MutableIterator).remove()
}
override fun nextDouble(): Double {
@ -267,7 +267,7 @@ fun <T> Iterator<T>.mapToInt(mapper: O2IFunction<T>): it.unimi.dsi.fastutil.ints
}
override fun remove() {
throw UnsupportedOperationException()
(this@mapToInt as MutableIterator).remove()
}
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)
}
fun <T> Iterator<T>.toList(): List<T> {
fun <T> Iterator<T>.toList(): MutableList<T> {
val result = ArrayList<T>()
result.addAll(this)
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 {
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

View File

@ -96,6 +96,9 @@ fun BigInteger.formatSiComponent(suffix: Any = "", decimalPlaces: Int = 3, forma
buffer[add + i + divided.length + 1] = prefix.paddedIndex(remainder, i)
}
if (suffix == "")
return TranslatableComponent(prefix.conciseFormatLocaleKey, String(buffer))
else
return TranslatableComponent(prefix.formatLocaleKey, String(buffer), suffix)
}

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.Reference2IntOpenHashMap
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.world.item.CreativeModeTabs
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraftforge.common.CreativeModeTabRegistry
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.nullsFirst
import ru.dbotthepony.mc.otm.core.nullsLast
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.suppliers
import ru.dbotthepony.mc.otm.matter.MatterManager
import ru.dbotthepony.mc.otm.storage.ItemStorageStack
import ru.dbotthepony.mc.otm.client.render.Widgets18
object CreativeMenuComparator : Comparator<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 {
rebuild()
return item2index.getInt(o1).compareTo(item2index.getInt(o2))
@ -84,6 +98,24 @@ object ItemLocalizedNameComparator : Comparator<Item> {
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> {
override fun compare(o1: Item, o2: Item): Int {
val a = o1.registryName?.namespace ?: return 0
@ -106,14 +138,65 @@ object ItemIDComparator : Comparator<Item> {
val NullsLast = nullsLast()
}
enum class ItemSorter(val comparator: Comparator<Item?>, private val sTitle: Component) {
DEFAULT(CreativeMenuComparator.NullsFirst, TranslatableComponent("otm.gui.sorting.default")),
NAME(ItemLocalizedNameComparator.NullsFirst.thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.name")),
ID(ItemIDComparator.NullsFirst.thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.id")),
MOD(ItemModComparator.NullsFirst.thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.modid")),
MATTER_VALUE(MatterValueComparator.NullsFirst.thenComparing(MatterComplexityComparator.NullsFirst).thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.matter_value")),
MATTER_COMPLEXITY(MatterComplexityComparator.NullsFirst.thenComparing(MatterValueComparator.NullsFirst).thenComparing(CreativeMenuComparator.NullsFirst), TranslatableComponent("otm.gui.sorting.matter_complexity")),
object ItemStackCountComparator : Comparator<ItemStack> {
override fun compare(o1: ItemStack, o2: ItemStack): Int {
return o1.count.compareTo(o2.count)
}
val NullsFirst = nullsFirst()
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
val formatLocaleKey = "otm.suffix.${name.lowercase()}".intern()
val conciseFormatLocaleKey = "otm.suffix_concise.${name.lowercase()}".intern()
val rawLocaleKey = "otm.suffix_raw.${name.lowercase()}".intern()
val string: String

View File

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

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.graph.storage
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import net.minecraft.world.level.block.entity.BlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
@ -13,7 +14,7 @@ import java.util.*
import java.util.function.Supplier
open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null) : GraphNode<StorageNode, StorageGraph>(::StorageGraph) {
protected val components = ArrayList<IStorage<*>>()
protected val components = ObjectArraySet<IStorage<*>>()
/**
* 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<*>) {
if (!components.any { component === it || it.storageType === component.storageType }) {
components.add(component)
if (components.add(component))
if (isValid && !manualAttaching)
graph.add(component)
}
}
fun removeStorageComponent(component: IStorage<*>) {
val indexOf = components.indexOfFirst { component === it || it.storageType === component.storageType }
if (indexOf == -1) return
val self = components.removeAt(indexOf)
if (isValid) graph.remove(self)
}
fun removeStorageComponent(component: StorageStack.Type<*>) {
val indexOf = components.indexOfFirst { it.storageType === component }
if (indexOf == -1) return
val self = components.removeAt(indexOf)
if (isValid) graph.remove(self)
if (components.remove(component) && isValid)
graph.remove(component)
}
override fun invalidate() {

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.MenuFieldPacket
import ru.dbotthepony.mc.otm.network.MenuNetworkChannel
import ru.dbotthepony.mc.otm.network.SetCarriedPacket
import ru.dbotthepony.mc.otm.network.enqueueWork
import ru.dbotthepony.mc.otm.network.packetHandled
import ru.dbotthepony.mc.otm.network.sender
@ -461,6 +462,16 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return player.distanceToSqr(pos.x.toDouble() + 0.5, pos.y.toDouble() + 0.5, pos.z.toDouble() + 0.5) <= 64.0
}
fun syncCarried() {
setRemoteCarried(carried.copy())
MenuNetworkChannel.send(ply as ServerPlayer, SetCarriedPacket(carried))
}
fun syncCarried(stack: ItemStack) {
carried = stack
syncCarried()
}
private val externalSlots = ConditionalSet<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
if (copy.isStackable) {
for (slot in slots) {
if (onlyFiltered && (slot !is UserFilteredSlot || slot.filter == null || slot.filter!!.get() != item.item)) {
if (onlyFiltered && (slot !is UserFilteredSlot || !slot.test(item))) {
continue
} else if (!onlyFiltered && slot is UserFilteredSlot && slot.filter != null && slot.filter!!.get() != null && slot.filter!!.get() != item.item) {
} else if (!onlyFiltered && slot is UserFilteredSlot && !slot.test(item)) {
continue
}

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

View File

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

View File

@ -3,7 +3,10 @@ package ru.dbotthepony.mc.otm.menu.input
import net.minecraft.world.entity.player.Player
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.ISubscriptable
import ru.dbotthepony.mc.otm.core.SentientGetterSetter
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.network.synchronizer.IField
import java.util.function.Consumer
import java.util.function.Predicate
import java.util.function.Supplier
@ -14,33 +17,7 @@ import kotlin.reflect.KMutableProperty0
*
* Getting and setting values should ONLY be done clientside
*/
interface IPlayerInputWithFeedback<V> : GetterSetter<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)
}
interface IPlayerInputWithFeedback<V> : SentientGetterSetter<V>, Predicate<Player?>
/**
* 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 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
}
override fun accept(t: V) {
final override fun accept(t: V) {
input.input(t)
}

View File

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

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: 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)
private val default = codec.values.first()
override val input = menu.PlayerInput(codec, false) { consumer?.invoke(it) }
override val value by menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec)
override val input = menu.PlayerInput(codec, allowSpectators) { consumer?.invoke(it) }
override val field = menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec)
constructor(menu: MatteryMenu, clazz: Class<E>, state: KMutableProperty0<E>?) : this(menu, clazz) {
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) {
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>() {
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) {
with(state)

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,12 @@
package ru.dbotthepony.mc.otm.network.synchronizer
import ru.dbotthepony.mc.otm.core.FloatSupplier
import ru.dbotthepony.mc.otm.core.IBooleanSubscriptable
import ru.dbotthepony.mc.otm.core.IDoubleSubcripable
import ru.dbotthepony.mc.otm.core.IFloatSubcripable
import ru.dbotthepony.mc.otm.core.IIntSubcripable
import ru.dbotthepony.mc.otm.core.ILongSubcripable
import ru.dbotthepony.mc.otm.core.ISubscriptable
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.function.BooleanSupplier
@ -11,7 +17,7 @@ import java.util.function.Supplier
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
sealed interface IField<V> : ReadOnlyProperty<Any?, V>, Supplier<V>, () -> V {
sealed interface IField<V> : ReadOnlyProperty<Any?, V>, Supplier<V>, () -> V, ISubscriptable<V> {
fun observe(): Boolean
fun markDirty()
fun markDirty(endpoint: FieldSynchronizer.Endpoint)
@ -40,7 +46,7 @@ interface IFloatProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Float
}
sealed interface IFloatField : IField<Float>, FloatSupplier {
sealed interface IFloatField : IField<Float>, FloatSupplier, IFloatSubcripable {
val float: Float
val property: IFloatProperty
@ -66,7 +72,7 @@ interface IDoubleProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Double
}
sealed interface IDoubleField : IField<Double>, DoubleSupplier {
sealed interface IDoubleField : IField<Double>, DoubleSupplier, IDoubleSubcripable {
val double: Double
val property: IDoubleProperty
@ -92,7 +98,7 @@ interface IIntProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int
}
sealed interface IIntField : IField<Int>, IntSupplier {
sealed interface IIntField : IField<Int>, IntSupplier, IIntSubcripable {
val int: Int
val property: IIntProperty
@ -118,7 +124,7 @@ interface ILongProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Long
}
sealed interface ILongField : IField<Long>, LongSupplier {
sealed interface ILongField : IField<Long>, LongSupplier, ILongSubcripable {
val long: Long
val property: ILongProperty
@ -144,7 +150,7 @@ interface IBooleanProperty {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean
}
sealed interface IBooleanField : IField<Boolean>, BooleanSupplier {
sealed interface IBooleanField : IField<Boolean>, BooleanSupplier, IBooleanSubscriptable {
val boolean: Boolean
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.floats.FloatConsumer
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.SentientGetterSetter
import java.util.function.DoubleConsumer
import java.util.function.IntConsumer
import java.util.function.LongConsumer
import kotlin.reflect.KProperty
sealed interface IMutableField<V> : IField<V>, GetterSetter<V> {
sealed interface IMutableField<V> : IField<V>, SentientGetterSetter<V> {
override fun getValue(thisRef: Any?, property: KProperty<*>): V {
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.capability.matter.matter
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.core.util.CreativeMenuComparator
import ru.dbotthepony.mc.otm.core.util.CreativeMenuItemComparator
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.registryName
@ -177,6 +177,9 @@ private fun addMainCreativeTabItems(consumer: CreativeModeTab.Output) {
accept(MItems.PATTERN_DRIVE_CREATIVE)
accept(MItems.PATTERN_DRIVE_CREATIVE2)
accept(MItems.PORTABLE_CONDENSATION_DRIVE)
accept(MItems.PORTABLE_DENSE_CONDENSATION_DRIVE)
fluids(MItems.FLUID_CAPSULE)
fluids(MItems.FLUID_TANK)
@ -285,7 +288,7 @@ object MCreativeTabs {
}
fun register(event: BuildCreativeModeTabContentsEvent) {
CreativeMenuComparator.invalidate()
CreativeMenuItemComparator.invalidate()
when (event.tab) {
MAIN -> addMainCreativeTabItems(event)

View File

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

View File

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

View File

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

View File

@ -63,9 +63,19 @@ abstract class StorageStack<S : StorageStack<S>>(val count: BigInteger) {
fun read(buff: FriendlyByteBuf): T
fun write(buff: FriendlyByteBuf, value: T)
fun energyPerUpkeep(stack: T): Decimal = Decimal.ZERO
/**
* If there is not enough energy for operation, it is completely cancelled
*/
fun energyPerInsert(stack: T): Decimal = energyPerOperation(stack)
/**
* If there is not enough energy for operation, it is completely cancelled
*/
fun energyPerExtract(stack: T): Decimal = energyPerOperation(stack)
/**
* If there is not enough energy for operation, it is completely cancelled
*/
fun energyPerOperation(stack: T): Decimal
}
@ -113,7 +123,7 @@ abstract class StorageStack<S : StorageStack<S>>(val count: BigInteger) {
ItemStorageStack.unsafe(it.readItem(), it.readBigInteger())
},
{ buff, v ->
buff.writeItem(v.toItemStack())
buff.writeItem(v.toItemStack(1))
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.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import ru.dbotthepony.mc.otm.core.math.isPositive
import ru.dbotthepony.mc.otm.core.math.isZero
import java.math.BigInteger
@ -41,23 +42,19 @@ class LocalTuple<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 -> Кортеж
protected val remoteTuples = Object2ObjectOpenHashMap<UUID, RemoteTuple<T>>()
private val remoteTuples = Object2ObjectOpenHashMap<UUID, RemoteTuple<T>>()
// локальный 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 для скорости работы
protected val listeners: MutableSet<IStorageEventConsumer<T>> = ObjectArraySet()
protected val children: MutableSet<IStorage<T>> = ObjectArraySet()
protected val consumers: MutableSet<IStorageAcceptor<T>> = ObjectArraySet()
protected open fun onAdd(identity: IStorage<T>) {}
protected open fun onRemove(identity: IStorage<T>) {}
private val listeners = ObjectLinkedOpenHashSet<IStorageEventConsumer<T>>()
private val children = ObjectLinkedOpenHashSet<IStorage<T>>()
private val consumers = ObjectLinkedOpenHashSet<IStorageAcceptor<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" }
@ -76,8 +73,6 @@ open class VirtualComponent<T : StorageStack<T>>(override val storageType: Stora
if (identity is IStorageAcceptor<T>) {
consumers.add(identity)
}
onAdd(identity)
}
}
@ -98,8 +93,6 @@ open class VirtualComponent<T : StorageStack<T>>(override val storageType: Stora
if (identity is IStorageAcceptor<T>) {
consumers.remove(identity)
}
onRemove(identity)
}
}
@ -231,4 +224,8 @@ open class VirtualComponent<T : StorageStack<T>>(override val storageType: Stora
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) {
override val storageType: StorageStack.Type<T>
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
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 {
val leftover = parent.insertStack(stack, true)
if (leftover == stack) return stack

View File

@ -1,6 +1,10 @@
package ru.dbotthepony.mc.otm.storage.powered
import it.unimi.dsi.fastutil.HashCommon.mix
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.storage.IStorageEventConsumer
import ru.dbotthepony.mc.otm.storage.IStorageProvider
import ru.dbotthepony.mc.otm.storage.StorageStack
import java.math.BigInteger
@ -8,8 +12,57 @@ import java.util.*
import java.util.function.Supplier
class PoweredStorageProvider<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 {
val extracted = parent.extractStack(id, amount, simulate)
val extracted = parent.extractStack(id, amount, true)
if (extracted.isEmpty) return extracted
val energy = energy.get()

View File

@ -16,6 +16,18 @@ class PoweredVirtualComponent<T : StorageStack<T>>(
) : IVirtualStorageComponent<T>, IStorageComponent<T> by PoweredComponent(parent, 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 onStackChanged(stack: T, id: UUID) = parent.onStackChanged(stack, id)
override fun onStackRemoved(id: UUID) = parent.onStackRemoved(id)