From 8a78b299c56ddce9af544a7675d5be225a6aa348 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Tue, 25 Jul 2023 14:19:35 +0700 Subject: [PATCH] Revamp job event loops, move jobs to codecs Add PatternState codecs Add ReplicationTask codecs Instead of returning job status on job tick, now downstream code directly update status of provided job status object Make Matter Replicator and Matter Recycler not do full stop when there is not enough matter/nowhere to put matter --- .../dbotthepony/mc/otm/block/entity/Jobs.kt | 323 +++++++++++++----- .../block/entity/MatteryWorkerBlockEntity.kt | 29 +- .../matter/MatterDecomposerBlockEntity.kt | 67 ++-- .../entity/matter/MatterPanelBlockEntity.kt | 8 +- .../matter/MatterRecyclerBlockEntity.kt | 73 ++-- .../matter/MatterReplicatorBlockEntity.kt | 171 +++------- .../entity/matter/MatterScannerBlockEntity.kt | 33 +- .../block/entity/tech/CobblerBlockEntity.kt | 16 +- .../entity/tech/PlatePressBlockEntity.kt | 22 +- .../entity/tech/PoweredFurnaceBlockEntity.kt | 21 +- .../otm/capability/MatteryPlayerCapability.kt | 23 +- .../mc/otm/capability/matter/PatternState.kt | 41 ++- .../otm/capability/matter/ReplicationTask.kt | 75 ++-- .../jade/providers/MatteryWorkerProvider.kt | 6 +- .../mc/otm/data/ComparableCodec.kt | 51 +++ .../dbotthepony/mc/otm/data/DecimalCodec.kt | 55 ++- .../kotlin/ru/dbotthepony/mc/otm/data/Ext.kt | 5 + .../ru/dbotthepony/mc/otm/data/UUIDCodec.kt | 26 ++ 18 files changed, 614 insertions(+), 431 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/data/ComparableCodec.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt index e6517b2df..33f9e9b5c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Jobs.kt @@ -1,114 +1,229 @@ package ru.dbotthepony.mc.otm.block.entity +import com.mojang.datafixers.Products +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps import net.minecraft.world.item.ItemStack import net.minecraftforge.common.util.INBTSerializable +import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.getDecimal -import ru.dbotthepony.mc.otm.core.math.set import ru.dbotthepony.mc.otm.core.math.weakEqualDoubles import ru.dbotthepony.mc.otm.core.math.weakGreaterThan import ru.dbotthepony.mc.otm.core.math.weakLessThan -import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.minRange private fun isReason(status: Any?, reason: Any) = status == null || status == reason +private val LOGGER = LogManager.getLogger() -interface IMachineJob { +interface IJob { val ticks: Double val powerUsage: Decimal val experience: Float get() = 0f - - fun serializeNBT(): CompoundTag } -open class MachineJob : IMachineJob { - final override val ticks: Double - final override val powerUsage: Decimal - final override val experience: Float +open class Job( + final override val ticks: Double, + final override val powerUsage: Decimal = Decimal.ZERO, + final override val experience: Float = 0f +) : IJob { + companion object { + fun basicCodec(builder: RecordCodecBuilder.Instance): Products.P3, Double, Decimal, Float> { + return builder.group( + Codec.doubleRange(0.0, Double.MAX_VALUE).fieldOf("ticks").forGetter(Job::ticks), + DecimalCodec.fieldOf("powerUsage").forGetter(Job::powerUsage), // не надо указывать минимальную энергию как 0, + // ибо мы можем таким образом использовать это для создания работ генератора + Codec.floatRange(0f, Float.MAX_VALUE).optionalFieldOf("experience", 0f).forGetter(Job::experience), + ) + } - constructor( - ticks: Double, - powerUsage: Decimal = Decimal.ZERO, - experience: Float = 0f, - ) { - this.ticks = ticks - this.powerUsage = powerUsage - this.experience = experience - } + fun plainCodec(builder: RecordCodecBuilder.Instance): Products.P2, Double, Decimal> { + return builder.group( + Codec.doubleRange(0.0, Double.MAX_VALUE).fieldOf("ticks").forGetter(Job::ticks), + DecimalCodec.fieldOf("powerUsage").forGetter(Job::powerUsage), // не надо указывать минимальную энергию как 0, + // ибо мы можем таким образом использовать это для создания работ генератора + ) + } - constructor( - tag: CompoundTag - ) : this(tag.getDouble("Ticks"), tag.getDecimal("EnergyUsage"), tag.getFloat("Experience")) - - override fun serializeNBT(): CompoundTag { - return CompoundTag().also { - it["Ticks"] = ticks - it["EnergyUsage"] = powerUsage - it["Experience"] = experience + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + basicCodec(it).apply(it, ::Job) + } } } } -open class MachineItemJob : MachineJob { - val itemStack: ItemStack +open class ItemJob( + val itemStack: ItemStack, + ticks: Double, + power: Decimal = Decimal.ZERO, + experience: Float = 0f +) : Job(ticks, power, experience) { + companion object { + fun itemCodec(builder: RecordCodecBuilder.Instance): Products.P4, ItemStack, Double, Decimal, Float> { + return builder.group(ItemStack.CODEC.fieldOf("itemStack").forGetter(ItemJob::itemStack)).and(basicCodec(builder)) + } - constructor( - itemStack: ItemStack, - ticks: Double, - power: Decimal = Decimal.ZERO, - experience: Float = 0f, - ) : super(ticks, power, experience) { - this.itemStack = itemStack - } - - constructor( - tag: CompoundTag - ) : super(tag) { - this.itemStack = (tag["Item"] as? CompoundTag)?.let { ItemStack.of(it) } ?: ItemStack.EMPTY - } - - override fun serializeNBT(): CompoundTag { - return super.serializeNBT().also { - it["Item"] = itemStack.serializeNBT() + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + itemCodec(it).apply(it, ::ItemJob) + } } } } -data class JobStatus( - val success: Boolean, - val throttleTicks: Int = 0, - val idleReason: MachineJobEventLoop.IdleReason? = null, - val newDrainedPower: Decimal? = null -) { - init { - require(throttleTicks >= 0) { "Negative amount of ticks to throttle: $throttleTicks" } +class JobStatus( + var requiredPower: Decimal, + val extractedPower: Decimal, + ticksAdvanced: Double, + val job: T, + val workTicks: Double, +) : IJob by job { + constructor(job: T, workTicks: Double) : this(Decimal.ZERO, Decimal.ZERO, 0.0, job, workTicks) + + val workProgress: Float + get() { + return ((workTicks + workTicks) / job.ticks).coerceAtMost(1.0).toFloat() + } + + var success = true + + var ticksAdvanced = ticksAdvanced + set(value) { + require(value >= 0.0) { "Invalid amount of ticks to advance: $value" } + field = value + } + + var throttleTicks = 0 + set(value) { + require(value >= 0) { "Invalid amount of ticks to throttle: $value" } + if (value != 0) success = false + field = value + } + + var idleReason: Any? = null + set(value) { + if (value != null) success = false + field = value + } + + /** + * You don't have to actually call this, JobStatus is in this state by default + * + * But this is handy to call this when doing early returns, to clearly state that "we are in success" + */ + fun success() { + this.success = true + this.throttleTicks = 0 + this.idleReason = null } - companion object { - val SUCCESS = JobStatus(true) - val FAILURE = JobStatus(false) - val FAILURE_ITEM = JobStatus(false, 20, MachineJobEventLoop.IdleReason.ITEM) - val FAILURE_MATTER = JobStatus(false, 20, MachineJobEventLoop.IdleReason.MATTER) - val FAILURE_WAIT = JobStatus(false, 100) - val FAILURE_WAIT_FAST = JobStatus(false, 20) + fun throttle() { + this.success = false + this.throttleTicks = 100 + } + + fun throttleFast() { + this.success = false + this.throttleTicks = 20 + } + + fun noItem(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.ITEM + this.throttleTicks = throttleTicks + } + + fun noPower(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.POWER + this.throttleTicks = throttleTicks + } + + fun noMatter(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.MATTER + this.throttleTicks = throttleTicks + } + + fun noPattern(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.PATTERN + this.throttleTicks = throttleTicks + } + + fun observe(throttleTicks: Int = 20) { + this.success = false + idleReason = MachineJobEventLoop.IdleReason.OBSERVING + this.throttleTicks = throttleTicks + } + + fun failure(reason: Any) { + this.success = false + idleReason = reason + } + + fun failure(throttleTicks: Int = 0) { + this.success = false + this.throttleTicks = throttleTicks + } + + fun scale(value: Decimal) { + require(value >= Decimal.ZERO) { "Attempted to scale by negative value: $value" } + if (value == Decimal.ONE) + return + + if (value.isZero) { + ticksAdvanced = 0.0 + requiredPower = Decimal.ZERO + success = false + } else { + requiredPower *= value + ticksAdvanced *= value.toDouble() + } + } + + fun scale(value: Double) { + require(value >= 0.0) { "Attempted to scale by negative value: $value" } + if (value == 1.0) + return + + if (value == 0.0) { + ticksAdvanced = 0.0 + requiredPower = Decimal.ZERO + success = false + } else { + requiredPower *= value + ticksAdvanced *= value + } } } -data class JobContainer(val job: JobType? = null, val idleReason: Any? = null, val throttleTicks: Int = 0) { +data class JobContainer(val job: JobType? = null, val idleReason: Any? = null, val throttleTicks: Int = 0) { init { require(throttleTicks >= 0) { "Negative amount of ticks to throttle: $throttleTicks" } + + if (job != null && idleReason != null) { + throw IllegalArgumentException("Can't have both job and idle reason specified") + } + + if (job != null && throttleTicks != 0) { + throw IllegalArgumentException("Can't have both job and throttle ticks specified") + } } companion object { - fun success(job: JobType): JobContainer { + fun success(job: JobType): JobContainer { return JobContainer(job, null) } - fun failure(reason: Any?): JobContainer { + fun failure(reason: Any?): JobContainer { return JobContainer(null, reason) } @@ -120,41 +235,40 @@ data class JobContainer(val job: JobType? = null, val idl private val observe = JobContainer(null, MachineJobEventLoop.IdleReason.OBSERVING) @Suppress("unchecked_cast") - fun failure(): JobContainer { + fun failure(): JobContainer { return empty as JobContainer } @Suppress("unchecked_cast") - fun noItem(): JobContainer { + fun noItem(): JobContainer { return noItem as JobContainer } @Suppress("unchecked_cast") - fun noEnergy(): JobContainer { + fun noEnergy(): JobContainer { return noEnergy as JobContainer } @Suppress("unchecked_cast") - fun noMatter(): JobContainer { + fun noMatter(): JobContainer { return noMatter as JobContainer } @Suppress("unchecked_cast") - fun noPattern(): JobContainer { + fun noPattern(): JobContainer { return noPattern as JobContainer } @Suppress("unchecked_cast") - fun observe(): JobContainer { + fun observe(): JobContainer { return observe as JobContainer } } } -abstract class MachineJobEventLoop : INBTSerializable { +abstract class MachineJobEventLoop(val codec: Codec) : INBTSerializable { protected abstract val energy: IMatteryEnergyStorage? protected abstract val isBlockedByRedstone: Boolean - protected abstract fun deserializeJob(nbt: CompoundTag): JobType? protected abstract val upgrades: IMatteryUpgrade? var currentJob: JobType? = null @@ -212,7 +326,17 @@ abstract class MachineJobEventLoop : INBTSerializable nbt["WorkTicks"] = workTicks - currentJob?.let { nbt["Job"] = it.serializeNBT() } + + currentJob?.let { + codec.encode(it, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get().map( + { + nbt["Job"] = it + }, + { + LOGGER.error("Failed to serialize job data, it will not persist", RuntimeException(it.message())) + } + ) + } } } @@ -220,27 +344,36 @@ abstract class MachineJobEventLoop : INBTSerializable) /** * Called when there is nothing to do and we are not idling */ protected abstract fun computeNextJob(): JobContainer - protected open fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: JobType): JobStatus { - return JobStatus.SUCCESS - } + protected open fun onJobTick(status: JobStatus) {} /** * Advances job loop by specified [ticks]. If machine is speed up by upgrades, @@ -317,8 +450,11 @@ abstract class MachineJobEventLoop : INBTSerializable : INBTSerializable : INBTSerializable( +abstract class MatteryWorkerBlockEntity( type: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState, - val jobDeserializer: (tag: CompoundTag) -> JobType?, + jobCodec: Codec, maxJobs: Int = 1, ) : MatteryPoweredBlockEntity(type, blockPos, blockState) { val jobEventLoops: ImmutableList> = immutableList(maxJobs) { id -> - object : MachineJobEventLoop() { + object : MachineJobEventLoop(jobCodec) { override val energy: IMatteryEnergyStorage? get() = matteryEnergy override val isBlockedByRedstone: Boolean @@ -41,12 +42,8 @@ abstract class MatteryWorkerBlockEntity( override val upgrades: IMatteryUpgrade? get() = this@MatteryWorkerBlockEntity.upgrades - override fun deserializeJob(nbt: CompoundTag): JobType? { - return jobDeserializer.invoke(nbt) - } - - override fun onJobFinish(job: JobType): JobStatus { - return this@MatteryWorkerBlockEntity.onJobFinish(job, id) + override fun onJobFinish(status: JobStatus) { + return this@MatteryWorkerBlockEntity.onJobFinish(status, id) } override fun computeNextJob(): JobContainer { @@ -57,8 +54,8 @@ abstract class MatteryWorkerBlockEntity( this@MatteryWorkerBlockEntity.jobUpdated(new, old, id) } - override fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: JobType): JobStatus { - return this@MatteryWorkerBlockEntity.onWorkTick(requiredPower, extractedPower, ticksAdvanced, job, id) + override fun onJobTick(status: JobStatus) { + return this@MatteryWorkerBlockEntity.onJobTick(status, id) } } } @@ -72,12 +69,10 @@ abstract class MatteryWorkerBlockEntity( } protected open fun jobUpdated(new: JobType?, old: JobType?, id: Int) {} - protected abstract fun onJobFinish(job: JobType, id: Int): JobStatus + protected abstract fun onJobFinish(status: JobStatus, id: Int) protected abstract fun computeNextJob(id: Int): JobContainer - protected open fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: JobType, id: Int): JobStatus { - return JobStatus.SUCCESS - } + protected open fun onJobTick(status: JobStatus, id: Int) {} override fun saveShared(nbt: CompoundTag) { super.saveShared(nbt) @@ -148,6 +143,8 @@ abstract class MatteryWorkerBlockEntity( } companion object { + private val LOGGER = LogManager.getLogger() + fun appendHoverText(itemStack: ItemStack, blockGetter: BlockGetter?, tooltips: MutableList, flag: TooltipFlag) { itemStack.tag ?: return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt index 3fdb11ce5..94af8254c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterDecomposerBlockEntity.kt @@ -1,5 +1,7 @@ package ru.dbotthepony.mc.otm.block.entity.matter +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.core.BlockPos import net.minecraft.nbt.CompoundTag import net.minecraft.world.entity.player.Inventory @@ -11,7 +13,7 @@ import net.minecraft.world.level.block.state.BlockState import net.minecraftforge.common.ForgeConfigSpec import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus -import ru.dbotthepony.mc.otm.block.entity.MachineJob +import ru.dbotthepony.mc.otm.block.entity.Job import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.MatteryCapability @@ -30,6 +32,8 @@ import ru.dbotthepony.mc.otm.core.math.getDecimal import ru.dbotthepony.mc.otm.core.math.set import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.util.WriteOnce +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.minRange import ru.dbotthepony.mc.otm.graph.matter.MatterGraph import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode import ru.dbotthepony.mc.otm.item.matter.MatterDustItem @@ -93,32 +97,23 @@ fun moveMatterAsDustIntoContainer(_matterValue: Decimal, container: MatteryConta } class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState) - : MatteryWorkerBlockEntity(MBlockEntities.MATTER_DECOMPOSER, pos, state, ::DecomposerJob) { - - class DecomposerJob : MachineJob { - val toDust: Boolean - var matterValue: Decimal - - constructor(tag: CompoundTag) : super(tag) { - toDust = tag.getBoolean(TO_DUST_KEY) - matterValue = tag.getDecimal(MATTER_VALUE_KEY) - } - - constructor(toDust: Boolean, matterValue: Decimal, ticks: Double) : super(ticks, BASE_CONSUMPTION) { - this.toDust = toDust - this.matterValue = matterValue - } - - override fun serializeNBT(): CompoundTag { - return super.serializeNBT().also { - it[TO_DUST_KEY] = toDust - it[MATTER_VALUE_KEY] = matterValue - } - } + : MatteryWorkerBlockEntity(MBlockEntities.MATTER_DECOMPOSER, pos, state, DecomposerJob.CODEC) { + class DecomposerJob( + val toDust: Boolean, + var matterValue: Decimal, + ticks: Double, + ) : Job(ticks, BASE_CONSUMPTION) { companion object { - const val TO_DUST_KEY = "toDust" - const val MATTER_VALUE_KEY = "matterValue" + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + Codec.BOOL.fieldOf("toDust").forGetter(DecomposerJob::toDust), + DecimalCodec.minRange(Decimal.ZERO).fieldOf("matterValue").forGetter(DecomposerJob::matterValue), + Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(DecomposerJob::ticks) + ).apply(it, ::DecomposerJob) + } + } } } @@ -165,22 +160,18 @@ class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState) return MatterDecomposerMenu(containerID, inventory, this) } - override fun onJobFinish(job: DecomposerJob, id: Int): JobStatus { - if (job.toDust) { - job.matterValue = moveMatterAsDustIntoContainer(job.matterValue, outputContainer, 0, 1) + override fun onJobFinish(status: JobStatus, id: Int) { + if (status.job.toDust) { + status.job.matterValue = moveMatterAsDustIntoContainer(status.job.matterValue, outputContainer, 0, 1) - if (!job.matterValue.isZero) - return JobStatus.FAILURE_WAIT_FAST + if (!status.job.matterValue.isZero) + status.throttleFast() + } else { + status.job.matterValue -= matter.receiveMatter(status.job.matterValue, false) - return JobStatus.SUCCESS + if (status.job.matterValue.isPositive) + status.noMatter() } - - job.matterValue -= matter.receiveMatter(job.matterValue, false) - - if (job.matterValue.isPositive) - return JobStatus.FAILURE_MATTER - - return JobStatus.SUCCESS } override fun computeNextJob(id: Int): JobContainer { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt index b51146f0e..12b9fc174 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterPanelBlockEntity.kt @@ -9,8 +9,6 @@ import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu import ru.dbotthepony.mc.otm.capability.MatteryCapability -import java.util.HashMap -import java.util.UUID import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag import net.minecraft.server.level.ServerLevel @@ -29,7 +27,7 @@ import ru.dbotthepony.mc.otm.graph.matter.MatterGraph import ru.dbotthepony.mc.otm.graph.matter.MatterNode import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode import ru.dbotthepony.mc.otm.registry.MBlockEntities -import java.util.ArrayList +import java.util.* import java.util.stream.Stream class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : @@ -100,7 +98,7 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : for ((key, task) in _tasks) { if (task.required > 0) { - val pattern = task.patternId?.let(graph::getPattern) ?: continue + val pattern = task.patternId.map(graph::getPattern).orElse(null) ?: continue if (!simulate) { val new = task.allocate() @@ -198,7 +196,7 @@ class MatterPanelBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : } fun addTask(state: IPatternState, count: Int): IReplicationTask<*> { - val task = ReplicationTask(UUID.randomUUID(), state.id, state.item, 0, 0, count) + val task = ReplicationTask(UUID.randomUUID(), Optional.of(state.id), state.item, 0, 0, count) _tasks[task.id] = task matterNode.graph.onMatterTaskCreated(task) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt index b47136e3f..b67971e95 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterRecyclerBlockEntity.kt @@ -1,22 +1,19 @@ package ru.dbotthepony.mc.otm.block.entity.matter +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.core.BlockPos -import net.minecraft.nbt.CompoundTag 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.Level import net.minecraft.world.level.block.state.BlockState -import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.common.ForgeConfigSpec.ConfigValue import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus -import ru.dbotthepony.mc.otm.block.entity.MachineJob -import ru.dbotthepony.mc.otm.config.ConciseBalanceValues +import ru.dbotthepony.mc.otm.block.entity.Job import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection -import ru.dbotthepony.mc.otm.capability.energy.BlockEnergyStorageImpl import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.WorkerEnergyStorage @@ -30,40 +27,20 @@ import ru.dbotthepony.mc.otm.graph.matter.MatterGraph import ru.dbotthepony.mc.otm.item.matter.MatterDustItem import ru.dbotthepony.mc.otm.menu.matter.MatterRecyclerMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities -import ru.dbotthepony.mc.otm.core.math.DecimalConfigValue -import ru.dbotthepony.mc.otm.core.math.defineDecimal -import ru.dbotthepony.mc.otm.registry.MNames -import ru.dbotthepony.mc.otm.core.util.WriteOnce -import ru.dbotthepony.mc.otm.core.math.getDecimal -import ru.dbotthepony.mc.otm.core.math.set +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.minRange import ru.dbotthepony.mc.otm.graph.matter.SimpleMatterNode class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) - : MatteryWorkerBlockEntity(MBlockEntities.MATTER_RECYCLER, blockPos, blockState, ::RecyclerJob) { - - class RecyclerJob : MachineJob { - var totalMatter: Decimal - - constructor( - ticks: Double, - powerUsage: Decimal, - totalMatter: Decimal - ) : super(ticks, powerUsage) { - this.totalMatter = totalMatter - } - - constructor(tag: CompoundTag) : super(tag) { - this.totalMatter = tag.getDecimal(KEY) - } - - override fun serializeNBT(): CompoundTag { - return super.serializeNBT().also { - it[KEY] = totalMatter - } - } + : MatteryWorkerBlockEntity(MBlockEntities.MATTER_RECYCLER, blockPos, blockState, RecyclerJob.CODEC) { + class RecyclerJob(ticks: Double, powerUsage: Decimal, var totalMatter: Decimal) : Job(ticks, powerUsage) { companion object { - const val KEY = "totalMatter" + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + plainCodec(it).and(DecimalCodec.minRange(Decimal.ZERO).fieldOf("totalMatter").forGetter(RecyclerJob::totalMatter)).apply(it, ::RecyclerJob) + } + } } } @@ -108,18 +85,18 @@ class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) return MatterRecyclerMenu(containerID, inventory, this) } - override fun onJobFinish(job: RecyclerJob, id: Int): JobStatus { + override fun onJobFinish(status: JobStatus, id: Int) { + val job = status.job + if (job.totalMatter.isPositive) { val received = matter.receiveMatter(job.totalMatter, true) if (job.totalMatter != received) - return JobStatus.FAILURE_MATTER + return status.noMatter() matter.receiveMatter(job.totalMatter, false) job.totalMatter -= received } - - return JobStatus.SUCCESS } override fun computeNextJob(id: Int): JobContainer { @@ -147,20 +124,16 @@ class MatterRecyclerBlockEntity(blockPos: BlockPos, blockState: BlockState) ) } - override fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: RecyclerJob, id: Int): JobStatus { - val receive = job.totalMatter.coerceAtMost(MachinesConfig.MATTER_RECYCLER_MATTER_PER_TICK.get() * ticksAdvanced) + override fun onJobTick(status: JobStatus, id: Int) { + val job = status.job + val toReceive = job.totalMatter.coerceAtMost(MachinesConfig.MATTER_RECYCLER_MATTER_PER_TICK.get() * status.ticksAdvanced) - if (receive.isZero) - return JobStatus.SUCCESS + if (toReceive.isZero) + return status.success() - val received = matter.receiveMatter(receive, true) - - if (receive != received) - return JobStatus.FAILURE_MATTER - - matter.receiveMatter(receive, false) + val received = matter.receiveMatter(toReceive, false) + status.scale(received / toReceive) job.totalMatter -= received - return JobStatus.SUCCESS } override fun tick() { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt index a71405fc6..8ee668a7b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterReplicatorBlockEntity.kt @@ -1,7 +1,8 @@ package ru.dbotthepony.mc.otm.block.entity.matter +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.core.BlockPos -import net.minecraft.nbt.CompoundTag import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Player import net.minecraft.world.inventory.AbstractContainerMenu @@ -11,7 +12,7 @@ import net.minecraft.world.level.block.state.BlockState import net.minecraftforge.common.ForgeConfigSpec import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus -import ru.dbotthepony.mc.otm.block.entity.MachineItemJob +import ru.dbotthepony.mc.otm.block.entity.ItemJob import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity import ru.dbotthepony.mc.otm.capability.FlowDirection @@ -28,76 +29,43 @@ import ru.dbotthepony.mc.otm.container.UpgradeContainer 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.math.getDecimal -import ru.dbotthepony.mc.otm.core.math.set -import ru.dbotthepony.mc.otm.core.nbt.map -import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.util.WriteOnce +import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.UUIDCodec +import ru.dbotthepony.mc.otm.data.minRange import ru.dbotthepony.mc.otm.graph.matter.MatterNode import ru.dbotthepony.mc.otm.matter.MatterManager import ru.dbotthepony.mc.otm.menu.matter.MatterReplicatorMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MNames +import java.util.* class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryWorkerBlockEntity(MBlockEntities.MATTER_REPLICATOR, p_155229_, p_155230_, { - try { - ReplicatorJob(it) - } catch(err: NoSuchElementException) { - null - } - }) { - - class ReplicatorJob : MachineItemJob { - val matterPerTick: Decimal - val task: ReplicationTask - var matterValue: Decimal - val pattern: PatternState? - val asDust: Boolean - - constructor(tag: CompoundTag) : super(tag) { - matterPerTick = tag.getDecimal(MATTER_PER_TICK_KEY) - matterValue = tag.getDecimal(MATTER_VALUE_KEY) - pattern = tag.map(PATTERN_KEY, PatternState::deserializeNBT) - asDust = tag.getBoolean(AS_DUST_KEY) - task = tag.map(TASK_KEY, ReplicationTask::deserializeNBT) ?: throw NoSuchElementException("Unable to deserialize matter task") - } - - constructor( - itemStack: ItemStack, - matterPerTick: Decimal, - task: ReplicationTask, - matterValue: Decimal, - pattern: PatternState?, - asDust: Boolean, - ticks: Double, - ) : super(itemStack, ticks, BASE_CONSUMPTION) { - this.matterPerTick = matterPerTick - this.task = task - this.matterValue = matterValue - this.pattern = pattern - this.asDust = asDust - } - - override fun serializeNBT(): CompoundTag { - return super.serializeNBT().also { - it[MATTER_PER_TICK_KEY] = this.matterPerTick - it[TASK_KEY] = this.task.serializeNBT() - it[MATTER_VALUE_KEY] = this.matterValue - - if (this.pattern != null) - it[PATTERN_KEY] = this.pattern.serializeNBT() - - it[AS_DUST_KEY] = this.asDust - } - } + MatteryWorkerBlockEntity(MBlockEntities.MATTER_REPLICATOR, p_155229_, p_155230_, ReplicatorJob.CODEC) { + class ReplicatorJob( + itemStack: ItemStack, + val matterPerTick: Decimal, + val task: UUID, + var matterValue: Decimal, + val pattern: Optional, + val asDust: Boolean, + ticks: Double, + ) : ItemJob(itemStack, ticks, BASE_CONSUMPTION) { companion object { - const val MATTER_PER_TICK_KEY = "matterPerTick" - const val MATTER_VALUE_KEY = "matterValue" - const val PATTERN_KEY = "pattern" - const val AS_DUST_KEY = "asDust" - const val TASK_KEY = "task" + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + it.group( + ItemStack.CODEC.fieldOf("itemStack").forGetter(ReplicatorJob::itemStack), + DecimalCodec.minRange(Decimal.ZERO).fieldOf("matterPerTick").forGetter(ReplicatorJob::matterPerTick), + UUIDCodec.fieldOf("task").forGetter(ReplicatorJob::task), + DecimalCodec.minRange(Decimal.ZERO).fieldOf("matterValue").forGetter(ReplicatorJob::matterValue), + PatternState.CODEC.optionalFieldOf("pattern").forGetter(ReplicatorJob::pattern), + Codec.BOOL.fieldOf("asDust").forGetter(ReplicatorJob::asDust), + Codec.doubleRange(0.0, Double.MAX_VALUE).fieldOf("ticks").forGetter(ReplicatorJob::ticks), + ).apply(it, ::ReplicatorJob) + } + } } } @@ -141,24 +109,24 @@ class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : return MatterReplicatorMenu(containerID, inventory, this) } - override fun onJobFinish(job: ReplicatorJob, id: Int): JobStatus { + override fun onJobFinish(status: JobStatus, id: Int) { + val job = status.job + if (job.asDust) { job.matterValue = moveMatterAsDustIntoContainer(job.matterValue, container, OUTPUT_DUST_MAIN, OUTPUT_DUST_STACKING) if (!job.matterValue.isZero) { - return JobStatus.FAILURE_WAIT + return status.throttle() } - matterNode.graph.notifyTaskCompletion(job.task.id) - return JobStatus.SUCCESS - } + matterNode.graph.notifyTaskCompletion(job.task) + } else { + if (!container.fullyAddItem(job.itemStack, FIRST_ACTUAL_OUTPUT_SLOT .. LAST_ACTUAL_OUTPUT_SLOT)) { + return status.noItem() + } - if (!container.fullyAddItem(job.itemStack, FIRST_ACTUAL_OUTPUT_SLOT .. LAST_ACTUAL_OUTPUT_SLOT)) { - return JobStatus.FAILURE_ITEM + matterNode.graph.notifyTaskCompletion(job.task) } - - matterNode.graph.notifyTaskCompletion(job.task.id) - return JobStatus.SUCCESS } override fun setRemoved() { @@ -211,64 +179,29 @@ class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : return JobContainer.success(ReplicatorJob( itemStack = stack, matterPerTick = matter.matter / ticks, - task = allocation.task.asImmutable(), + task = allocation.task.id, matterValue = matter.matter, - pattern = allocation.pattern?.asImmutable(), + pattern = Optional.ofNullable(allocation.pattern?.asImmutable()), asDust = (level?.random?.nextDouble() ?: 1.0) * upgrades.failureMultiplier > (allocation.pattern?.researchPercent ?: 2.0), ticks = ticks, )) } - override fun onWorkTick(requiredPower: Decimal, extractedPower: Decimal, ticksAdvanced: Double, job: ReplicatorJob, id: Int): JobStatus { - val drainPerTick = job.matterPerTick * ticksAdvanced + override fun onJobTick(status: JobStatus, id: Int) { + val job = status.job + val drainPerTick = job.matterPerTick * status.ticksAdvanced if (matter.extractMatter(drainPerTick, true) < drainPerTick) { - // в машине недостаточно материи + val toDrain = (drainPerTick * DRAIN_MULT) + .coerceAtMost(job.matterPerTick * (status.ticks - status.workTicks + status.ticksAdvanced)) + .coerceAtLeast(Decimal.ONE) + .coerceAtMost(matter.missingMatter) - if (drainPerTick > matter.maxStoredMatter) { - // в тик требуется больше материи, чем её может хранить репликатор - val toExtract = drainPerTick - matter.extractMatter(drainPerTick, true) - val drain = matterNode.graph.extractMatter(toExtract, true) - - if (drain != toExtract) { - // недостаточно материи в сети - return JobStatus.FAILURE_MATTER - } - - // достаточно материи в сети + внутри машины - matter.extractMatter(drainPerTick, false) - matterNode.graph.extractMatter(drain, false) - return JobStatus.SUCCESS - } else { - // в тик требуется меньше материи, чем её может хранить репликатор - // примем из сети недостающее количество бака материи, или 200 тиков репликации, что меньше - val drain = matterNode.graph.extractMatter((drainPerTick * DRAIN_MULT) - .coerceAtMost(job.matterPerTick * (job.ticks - jobEventLoops[0].workTicks - ticksAdvanced)) - .coerceAtLeast(Decimal.ONE) - .coerceAtMost(matter.missingMatter), false) - - if (drain.isZero) { - // в сети нет материи - return JobStatus.FAILURE_MATTER - } - - matter.receiveMatter(drain, false) - - // получили материю, проверяем возможность работы - if (matter.extractMatter(drainPerTick, true) >= drainPerTick) { - matter.extractMatter(drainPerTick, false) - return JobStatus.SUCCESS - } else { - // :( - return JobStatus.FAILURE_WAIT - } - } + matter.receiveMatter(matterNode.graph.extractMatter(toDrain, false), false) } - // в машине достаточно материи - matter.extractMatter(drainPerTick, false) - visualProgress = jobEventLoops[0].workProgress - return JobStatus.SUCCESS + status.scale(matter.extractMatter(drainPerTick, false) / drainPerTick) + visualProgress = status.workProgress } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt index ad51f4083..683957405 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterScannerBlockEntity.kt @@ -10,7 +10,7 @@ import net.minecraft.world.level.block.state.BlockState import net.minecraftforge.common.ForgeConfigSpec import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus -import ru.dbotthepony.mc.otm.block.entity.MachineItemJob +import ru.dbotthepony.mc.otm.block.entity.ItemJob import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop import ru.dbotthepony.mc.otm.config.ConciseBalanceValues import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity @@ -35,7 +35,7 @@ import java.util.* import kotlin.math.pow class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : - MatteryWorkerBlockEntity(MBlockEntities.MATTER_SCANNER, p_155229_, p_155230_, ::MachineItemJob) { + MatteryWorkerBlockEntity(MBlockEntities.MATTER_SCANNER, p_155229_, p_155230_, ItemJob.CODEC) { val container = MatteryContainer(::itemContainerUpdated, 1).also(::addDroppableContainer) val energy = ProfiledEnergyStorage(WorkerEnergyStorage(::energyLevelUpdated, ENERGY_VALUES)) @@ -87,9 +87,9 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : return MatterScannerMenu(containerID, inventory, this) } - override fun onJobFinish(job: MachineItemJob, id: Int): JobStatus { - val stack = job.itemStack - if (stack.isEmpty || !MatterManager.hasMatterValue(stack)) return JobStatus.SUCCESS + override fun onJobFinish(status: JobStatus, id: Int) { + val stack = status.job.itemStack + if (stack.isEmpty || !MatterManager.hasMatterValue(stack)) return status.success() var findState: IPatternState? = null @@ -110,14 +110,12 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : PatternState(UUID.randomUUID(), stack.item, researchAdvance) } - if (!matterNode.graph.insertPattern(new, onlyUpdate = false, simulate = false).isFailed) { - return JobStatus.SUCCESS - } else { - return JobStatus.FAILURE_WAIT + if (matterNode.graph.insertPattern(new, onlyUpdate = false, simulate = false).isFailed) { + status.throttle() } } - override fun computeNextJob(id: Int): JobContainer { + override fun computeNextJob(id: Int): JobContainer { if (energy.batteryLevel.isZero) { return JobContainer.noEnergy() } @@ -151,7 +149,7 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : stack.shrink(1) container.setChanged() val complexity = MatterManager.get(copy).complexity - return JobContainer.success(MachineItemJob(copy, (if (complexity > 1.0) complexity.pow(1.25) else complexity.pow(0.5)), BASE_CONSUMPTION)) + return JobContainer.success(ItemJob(copy, (if (complexity > 1.0) complexity.pow(1.25) else complexity.pow(0.5)), BASE_CONSUMPTION)) } return JobContainer.noItem() @@ -163,7 +161,7 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : } - override fun jobUpdated(new: MachineItemJob?, old: MachineItemJob?, id: Int) { + override fun jobUpdated(new: ItemJob?, old: ItemJob?, id: Int) { visualItemStack = new?.itemStack ?: ItemStack.EMPTY visualProgress = 0f } @@ -174,14 +172,11 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : var visualProgress by synchronizer.float().property private set - override fun onWorkTick( - requiredPower: Decimal, - extractedPower: Decimal, - ticksAdvanced: Double, - job: MachineItemJob, + override fun onJobTick( + status: JobStatus, id: Int - ): JobStatus { - val result = super.onWorkTick(requiredPower, extractedPower, ticksAdvanced, job, id) + ) { + val result = super.onJobTick(status, id) visualProgress = jobEventLoops[0].workProgress diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt index 04249f93d..c6f20e2d0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/CobblerBlockEntity.kt @@ -7,7 +7,7 @@ import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import net.minecraft.world.level.block.state.BlockState -import ru.dbotthepony.mc.otm.block.entity.MachineItemJob +import ru.dbotthepony.mc.otm.block.entity.ItemJob import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity @@ -17,7 +17,7 @@ import ru.dbotthepony.mc.otm.menu.tech.CobblerMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities class CobblerBlockEntity(blockPos: BlockPos, blockState: BlockState) - : MatteryWorkerBlockEntity(MBlockEntities.COBBLESTONE_GENERATOR, blockPos, blockState, ::MachineItemJob) { + : MatteryWorkerBlockEntity(MBlockEntities.COBBLESTONE_GENERATOR, blockPos, blockState, ItemJob.CODEC) { override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { return CobblerMenu(containerID, inventory, this) } @@ -36,16 +36,14 @@ class CobblerBlockEntity(blockPos: BlockPos, blockState: BlockState) savetable(::container, INVENTORY_KEY) } - override fun onJobFinish(job: MachineItemJob, id: Int): JobStatus { - if (!container.fullyAddItem(job.itemStack)) { - return JobStatus.FAILURE_ITEM + override fun onJobFinish(status: JobStatus, id: Int) { + if (!container.fullyAddItem(status.job.itemStack)) { + status.noItem() } - - return JobStatus.SUCCESS } - override fun computeNextJob(id: Int): JobContainer { - return JobContainer.success(MachineItemJob(ItemStack(Items.COBBLESTONE), 40.0)) + override fun computeNextJob(id: Int): JobContainer { + return JobContainer.success(ItemJob(ItemStack(Items.COBBLESTONE), 40.0)) } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt index e973cc320..df68623fc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt @@ -10,7 +10,7 @@ import net.minecraft.world.inventory.AbstractContainerMenu import net.minecraft.world.level.block.state.BlockState import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus -import ru.dbotthepony.mc.otm.block.entity.MachineItemJob +import ru.dbotthepony.mc.otm.block.entity.ItemJob import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity import ru.dbotthepony.mc.otm.capability.UpgradeType import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage @@ -20,7 +20,6 @@ import ru.dbotthepony.mc.otm.container.MatteryContainer import ru.dbotthepony.mc.otm.container.HandlerFilter import ru.dbotthepony.mc.otm.container.UpgradeContainer import ru.dbotthepony.mc.otm.container.balance -import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.menu.tech.PlatePressMenu import ru.dbotthepony.mc.otm.menu.tech.TwinPlatePressMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities @@ -30,7 +29,7 @@ class PlatePressBlockEntity( p_155229_: BlockPos, p_155230_: BlockState, val isTwin: Boolean = false, -) : MatteryWorkerBlockEntity(if (isTwin) MBlockEntities.TWIN_PLATE_PRESS else MBlockEntities.PLATE_PRESS, p_155229_, p_155230_, ::MachineItemJob, if (isTwin) 2 else 1) { +) : MatteryWorkerBlockEntity(if (isTwin) MBlockEntities.TWIN_PLATE_PRESS else MBlockEntities.PLATE_PRESS, p_155229_, p_155230_, ItemJob.CODEC, if (isTwin) 2 else 1) { override val upgrades = UpgradeContainer(this::setChangedLight, if (isTwin) 4 else 3, UpgradeType.BASIC_PROCESSING) val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(MachinesConfig.PLATE_PRESS))) val inputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer) @@ -69,18 +68,17 @@ class PlatePressBlockEntity( return PlatePressMenu(containerID, inventory, this) } - override fun onJobFinish(job: MachineItemJob, id: Int): JobStatus { - if (job.itemStack.isEmpty) - return JobStatus.SUCCESS + override fun onJobFinish(status: JobStatus, id: Int) { + if (status.job.itemStack.isEmpty) + return status.success() - if (!outputContainer.fullyAddItem(job.itemStack, start = id, end = id) && !outputContainer.fullyAddItem(job.itemStack)) - return JobStatus.FAILURE_ITEM + if (!outputContainer.fullyAddItem(status.job.itemStack, start = id, end = id) && !outputContainer.fullyAddItem(status.job.itemStack)) + return status.noItem() - experience = (experience + job.experience).coerceAtMost(100.0) - return JobStatus.SUCCESS + experience = (experience + status.experience).coerceAtMost(100.0) } - override fun computeNextJob(id: Int): JobContainer { + override fun computeNextJob(id: Int): JobContainer { if (energy.batteryLevel.isZero) { return JobContainer.noEnergy() } @@ -101,7 +99,7 @@ class PlatePressBlockEntity( inputContainer.setChanged(id) return JobContainer.success( - MachineItemJob( + ItemJob( recipe.getResultItem(level.registryAccess()).copyWithCount(toProcess), recipe.workTime * MachinesConfig.PLATE_PRESS.workTimeMultiplier, MachinesConfig.PLATE_PRESS.powerConsumption * toProcess, diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PoweredFurnaceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PoweredFurnaceBlockEntity.kt index afcdcdaac..e11236c03 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PoweredFurnaceBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PoweredFurnaceBlockEntity.kt @@ -3,19 +3,17 @@ package ru.dbotthepony.mc.otm.block.entity.tech import net.minecraft.core.BlockPos import net.minecraft.server.level.ServerLevel import net.minecraft.server.level.ServerPlayer -import net.minecraft.world.Container import net.minecraft.world.entity.ExperienceOrb 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.crafting.AbstractCookingRecipe -import net.minecraft.world.item.crafting.Recipe import net.minecraft.world.item.crafting.RecipeType import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.state.BlockState import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus -import ru.dbotthepony.mc.otm.block.entity.MachineItemJob +import ru.dbotthepony.mc.otm.block.entity.ItemJob import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity import ru.dbotthepony.mc.otm.capability.CombinedItemHandler import ru.dbotthepony.mc.otm.capability.UpgradeType @@ -36,7 +34,7 @@ class PoweredFurnaceBlockEntity( blockState: BlockState, val recipeType: RecipeType, val config: WorkerBalanceValues -) : MatteryWorkerBlockEntity(type, blockPos, blockState, ::MachineItemJob, 2) { +) : MatteryWorkerBlockEntity(type, blockPos, blockState, ItemJob.CODEC, 2) { override val upgrades = UpgradeContainer(this::setChangedLight, 2, UpgradeType.BASIC_PROCESSING) val energy = ProfiledEnergyStorage(WorkerEnergyStorage(this::energyLevelUpdated, upgrades.transform(config))) @@ -86,16 +84,15 @@ class PoweredFurnaceBlockEntity( return PoweredFurnaceMenu(containerID, inventory, this) } - override fun onJobFinish(job: MachineItemJob, id: Int): JobStatus { - if (outputs[id].fullyAddItem(job.itemStack)) { - experience += job.experience - return JobStatus.SUCCESS + override fun onJobFinish(status: JobStatus, id: Int) { + if (outputs[id].fullyAddItem(status.job.itemStack)) { + experience += status.experience + } else { + status.noItem() } - - return JobStatus.FAILURE_ITEM } - override fun computeNextJob(id: Int): JobContainer { + override fun computeNextJob(id: Int): JobContainer { if (!energy.batteryLevel.isPositive) return JobContainer.noEnergy() @@ -109,7 +106,7 @@ class PoweredFurnaceBlockEntity( val toProcess = inputs[id][0].count.coerceAtMost(upgrades.processingItems + 1) inputs[id][0].shrink(toProcess) - JobContainer.success(MachineItemJob( + JobContainer.success(ItemJob( output.copyWithCount(toProcess), it.cookingTime * config.workTimeMultiplier, config.powerConsumption * toProcess, it.experience * toProcess )) }.orElse(JobContainer.noItem()) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt index 1896bbc82..e7d735336 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt @@ -63,7 +63,7 @@ import ru.dbotthepony.mc.otm.android.AndroidResearchManager import ru.dbotthepony.mc.otm.android.AndroidResearchType import ru.dbotthepony.mc.otm.block.entity.JobContainer import ru.dbotthepony.mc.otm.block.entity.JobStatus -import ru.dbotthepony.mc.otm.block.entity.MachineItemJob +import ru.dbotthepony.mc.otm.block.entity.ItemJob import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop import ru.dbotthepony.mc.otm.capability.energy.BatteryBackedEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage @@ -411,33 +411,26 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial } }).property - inner class SmelterBelt(index: Int) : MachineJobEventLoop() { + inner class SmelterBelt(index: Int) : MachineJobEventLoop(ItemJob.CODEC) { override val energy: IMatteryEnergyStorage get() = exoPackEnergy override val isBlockedByRedstone: Boolean get() = false - override fun deserializeJob(nbt: CompoundTag): MachineItemJob { - return MachineItemJob(nbt) - } - override val upgrades: IMatteryUpgrade? get() = null - override fun jobUpdated(new: MachineItemJob?, old: MachineItemJob?) {} - - override fun onJobFinish(job: MachineItemJob): JobStatus { - if (output.fullyAddItem(job.itemStack)) { - exoPackSmelterExperience += job.experience - return JobStatus.SUCCESS + override fun onJobFinish(status: JobStatus) { + if (output.fullyAddItem(status.job.itemStack)) { + exoPackSmelterExperience += status.job.experience } else { - return JobStatus.FAILURE_ITEM + status.noItem() } } private val cache = RecipeManager.createCheck(RecipeType.SMELTING) - override fun computeNextJob(): JobContainer { + override fun computeNextJob(): JobContainer { if (!exoPackEnergy.batteryLevel.isPositive) return JobContainer.noEnergy() val level = ply.level() as? ServerLevel ?: return JobContainer.failure() val recipe = cache.getRecipeFor(input, level) @@ -449,7 +442,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial val item = actual.assemble(input, level.registryAccess()) input[0].shrink(1) input.setChanged(0) - return JobContainer.success(MachineItemJob(item, actual.cookingTime.toDouble(), ExopackConfig.FURNACE_POWER_CONSUMPTION, actual.experience)) + return JobContainer.success(ItemJob(item, actual.cookingTime.toDouble(), ExopackConfig.FURNACE_POWER_CONSUMPTION, actual.experience)) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt index c1c17a410..a982c17b5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/PatternState.kt @@ -1,6 +1,10 @@ package ru.dbotthepony.mc.otm.capability.matter +import com.mojang.datafixers.Products +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps import net.minecraft.nbt.Tag import net.minecraft.network.FriendlyByteBuf import net.minecraft.resources.ResourceLocation @@ -11,6 +15,7 @@ import net.minecraftforge.registries.ForgeRegistries import ru.dbotthepony.mc.otm.core.* import ru.dbotthepony.mc.otm.core.nbt.contains import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.data.UUIDCodec import java.util.* sealed interface IPatternState { @@ -45,13 +50,7 @@ sealed interface IPatternState { return ItemStack(item, count) } - fun serializeNBT(): CompoundTag { - return CompoundTag().also { - it["id"] = id - it["item"] = item.registryName!!.toString() - it["researchPercent"] = researchPercent - } - } + fun serializeNBT(): CompoundTag fun write(buff: FriendlyByteBuf) { buff.writeUUID(id) @@ -60,6 +59,14 @@ sealed interface IPatternState { } } +private fun codec(builder: RecordCodecBuilder.Instance): Products.P3, UUID, Item, Double> { + return builder.group( + UUIDCodec.fieldOf("id").forGetter(IPatternState::id), + ForgeRegistries.ITEMS.codec.fieldOf("item").forGetter(IPatternState::item), + Codec.doubleRange(0.0, 1.0).fieldOf("researchPercent").forGetter(IPatternState::researchPercent) + ) +} + private fun deserializeNBT(nbt: Tag?, mutable: Boolean): IPatternState? { if (nbt !is CompoundTag) return null @@ -108,6 +115,10 @@ data class PatternState( return this } + override fun serializeNBT(): CompoundTag { + return CODEC.encode(this, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get().map({ it as CompoundTag }, { throw RuntimeException("Failed to serialize PatternState: ${it.message()}") }) + } + companion object { fun deserializeNBT(tag: Tag?): PatternState? { return deserializeNBT(tag, false) as PatternState? @@ -116,6 +127,12 @@ data class PatternState( fun read(buff: FriendlyByteBuf): PatternState? { return read(buff, false) as PatternState? } + + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + codec(it).apply(it, ::PatternState) + } + } } } @@ -132,6 +149,10 @@ data class MutablePatternState( return PatternState(id, item, researchPercent) } + override fun serializeNBT(): CompoundTag { + return CODEC.encode(this, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get().map({ it as CompoundTag }, { throw RuntimeException("Failed to serialize MutablePatternState: ${it.message()}") }) + } + companion object { fun deserializeNBT(tag: Tag?): MutablePatternState? { return deserializeNBT(tag, true) as MutablePatternState? @@ -140,6 +161,12 @@ data class MutablePatternState( fun read(buff: FriendlyByteBuf): MutablePatternState? { return read(buff, true) as MutablePatternState? } + + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + codec(it).apply(it, ::MutablePatternState) + } + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt index b93b12d7f..234293cff 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/matter/ReplicationTask.kt @@ -1,6 +1,10 @@ package ru.dbotthepony.mc.otm.capability.matter +import com.mojang.datafixers.Products +import com.mojang.serialization.Codec +import com.mojang.serialization.codecs.RecordCodecBuilder import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.NbtOps import net.minecraft.nbt.Tag import net.minecraft.network.FriendlyByteBuf import net.minecraft.resources.ResourceLocation @@ -9,14 +13,14 @@ import net.minecraft.world.item.ItemStack import net.minecraft.world.item.Items import net.minecraftforge.registries.ForgeRegistries import ru.dbotthepony.mc.otm.core.readItemType -import ru.dbotthepony.mc.otm.core.registryName -import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.writeItemType +import ru.dbotthepony.mc.otm.data.UUIDCodec +import java.util.Optional import java.util.UUID sealed interface IReplicationTask> { val id: UUID - val patternId: UUID? + val patternId: Optional val item: Item val inProgress: Int val finished: Int @@ -29,7 +33,7 @@ sealed interface IReplicationTask> { fun copyAsMutable( id: UUID = this.id, - patternId: UUID? = this.patternId, + patternId: Optional = this.patternId, item: Item = this.item, inProgress: Int = this.inProgress, finished: Int = this.finished, @@ -40,7 +44,7 @@ sealed interface IReplicationTask> { fun copyAsImmutable( id: UUID = this.id, - patternId: UUID? = this.patternId, + patternId: Optional = this.patternId, item: Item = this.item, inProgress: Int = this.inProgress, finished: Int = this.finished, @@ -60,25 +64,13 @@ sealed interface IReplicationTask> { fun allocate(amount: Int = 1): S fun finish(amount: Int = 1): S - fun serializeNBT(): CompoundTag { - return CompoundTag().also { - it["id"] = id - - if (patternId != null) - it["patternId"] = patternId!! - - it["item"] = item.registryName!!.toString() - it["inProgress"] = inProgress - it["finished"] = finished - it["required"] = required - } - } + fun serializeNBT(): CompoundTag fun write(buff: FriendlyByteBuf) { buff.writeUUID(id) - buff.writeBoolean(patternId != null) - patternId?.let(buff::writeUUID) + buff.writeBoolean(patternId.isPresent) + patternId.ifPresent(buff::writeUUID) buff.writeItemType(item) @@ -88,6 +80,17 @@ sealed interface IReplicationTask> { } } +private fun > codec(builder: RecordCodecBuilder.Instance): Products.P6, UUID, Optional, Item, Int, Int, Int> { + return builder.group( + UUIDCodec.fieldOf("id").forGetter(IReplicationTask<*>::id), + UUIDCodec.optionalFieldOf("patternId").forGetter(IReplicationTask<*>::patternId), + ForgeRegistries.ITEMS.codec.fieldOf("item").forGetter(IReplicationTask<*>::item), + Codec.intRange(0, Int.MAX_VALUE).fieldOf("inProgress").forGetter(IReplicationTask<*>::inProgress), + Codec.intRange(0, Int.MAX_VALUE).fieldOf("finished").forGetter(IReplicationTask<*>::finished), + Codec.intRange(0, Int.MAX_VALUE).fieldOf("required").forGetter(IReplicationTask<*>::required), + ) +} + private fun deserializeNBT(nbt: Tag?, mutable: Boolean): IReplicationTask<*>? { if (nbt !is CompoundTag) return null @@ -108,9 +111,9 @@ private fun deserializeNBT(nbt: Tag?, mutable: Boolean): IReplicationTask<*>? { val required = nbt.getInt("required") if (mutable) { - return MutableReplicationTask(id, patternId, item, inProgress, finished, required) + return MutableReplicationTask(id, Optional.ofNullable(patternId), item, inProgress, finished, required) } else { - return ReplicationTask(id, patternId, item, inProgress, finished, required) + return ReplicationTask(id, Optional.ofNullable(patternId), item, inProgress, finished, required) } } @@ -125,15 +128,15 @@ private fun read(buff: FriendlyByteBuf, mutable: Boolean): IReplicationTask<*>? item ?: return null if (mutable) { - return MutableReplicationTask(id, patternId, item, inProgress, finished, required) + return MutableReplicationTask(id, Optional.ofNullable(patternId), item, inProgress, finished, required) } else { - return ReplicationTask(id, patternId, item, inProgress, finished, required) + return ReplicationTask(id, Optional.ofNullable(patternId), item, inProgress, finished, required) } } data class ReplicationTask( override val id: UUID, - override val patternId: UUID?, + override val patternId: Optional, override val item: Item, override val inProgress: Int, override val finished: Int, @@ -155,6 +158,10 @@ data class ReplicationTask( return ReplicationTask(id, patternId, item, inProgress - amount, finished + amount, required) } + override fun serializeNBT(): CompoundTag { + return CODEC.encode(this, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get().map({ it as CompoundTag }, { throw RuntimeException("Failed to serialize ReplicationTask: ${it.message()}") }) + } + companion object { fun deserializeNBT(nbt: Tag?): ReplicationTask? { return deserializeNBT(nbt, false) as ReplicationTask? @@ -163,12 +170,18 @@ data class ReplicationTask( fun read(buff: FriendlyByteBuf): ReplicationTask? { return read(buff, false) as ReplicationTask? } + + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + codec(it).apply(it, ::ReplicationTask) + } + } } } data class MutableReplicationTask( override val id: UUID, - override val patternId: UUID?, + override val patternId: Optional, override val item: Item, override var inProgress: Int, override var finished: Int, @@ -194,6 +207,10 @@ data class MutableReplicationTask( return this } + override fun serializeNBT(): CompoundTag { + return CODEC.encode(this, NbtOps.INSTANCE, NbtOps.INSTANCE.empty()).get().map({ it as CompoundTag }, { throw RuntimeException("Failed to serialize MutableReplicationTask: ${it.message()}") }) + } + companion object { fun deserializeNBT(nbt: Tag?): MutableReplicationTask? { return deserializeNBT(nbt, true) as MutableReplicationTask? @@ -202,6 +219,12 @@ data class MutableReplicationTask( fun read(buff: FriendlyByteBuf): MutableReplicationTask? { return read(buff, true) as MutableReplicationTask? } + + val CODEC: Codec by lazy { + RecordCodecBuilder.create { + codec(it).apply(it, ::MutableReplicationTask) + } + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt index a0b62cda3..9da096393 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/jade/providers/MatteryWorkerProvider.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.compat.jade.providers import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.ListTag import net.minecraft.resources.ResourceLocation -import ru.dbotthepony.mc.otm.block.entity.MachineItemJob +import ru.dbotthepony.mc.otm.block.entity.ItemJob import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity import ru.dbotthepony.mc.otm.compat.jade.JadeTagKeys import ru.dbotthepony.mc.otm.compat.jade.JadeUids @@ -34,8 +34,8 @@ object MatteryWorkerProvider : IBlockComponentProvider, IServerDataProvider>(val parent: Codec, val min: S? = null, val max: S? = null, val minExclusive: Boolean = false, val maxExclusive: Boolean = false) : Codec { + private fun check(input: S): DataResult? { + if (min != null) { + if (minExclusive) { + if (input <= min) { + return DataResult.error { "Value $input is smaller or equal to minimal $min" } + } + } else { + if (input < min) { + return DataResult.error { "Value $input is smaller than minimal $min" } + } + } + } + + if (max != null) { + if (maxExclusive) { + if (input >= max) { + return DataResult.error { "Value $input is bigger or equal to maximal $max" } + } + } else { + if (input > max) { + return DataResult.error { "Value $input is bigger than maximal $max" } + } + } + } + + return null + } + + override fun encode(input: S, ops: DynamicOps, prefix: T): DataResult { + check(input)?.let { return it } + return parent.encode(input, ops, prefix) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return parent.decode(ops, input).flatMap { + check>(it.first) ?: DataResult.success(it) + } + } +} + +fun > Codec.minRange(min: S, exclusive: Boolean = false) = ComparableCodec(this, min = min, minExclusive = exclusive) +fun > Codec.maxRange(max: S, exclusive: Boolean = false) = ComparableCodec(this, max = max, maxExclusive = exclusive) +fun > Codec.inRange(min: S, minExclusive: Boolean = false, max: S, maxExclusive: Boolean = false) = ComparableCodec(this, min, max, minExclusive, maxExclusive) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt index 140bd0848..dccd158c9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt @@ -4,28 +4,63 @@ import com.mojang.datafixers.util.Pair import com.mojang.serialization.Codec import com.mojang.serialization.DataResult import com.mojang.serialization.DynamicOps +import it.unimi.dsi.fastutil.bytes.ByteArrayList +import net.minecraft.nbt.NbtOps import ru.dbotthepony.mc.otm.core.math.Decimal +import java.nio.ByteBuffer +import java.util.stream.Collector +import java.util.stream.Stream object DecimalCodec : Codec { override fun encode(input: Decimal, ops: DynamicOps, prefix: T): DataResult { + if (ops === NbtOps.INSTANCE) { + return DataResult.success((ops as DynamicOps).createByteList(ByteBuffer.wrap(input.toByteArray()))) + } + return DataResult.success(ops.createString(input.toString())) } override fun decode(ops: DynamicOps, input: T): DataResult> { - val result = ops.getStringValue(input).flatMap { + return ops.getStringValue(input).flatMap { try { DataResult.success(Pair(Decimal(it), ops.empty())) } catch (err: NumberFormatException) { DataResult.error { "Not a valid number for converting into Decimal: $it" } } - } - - if (result.result().isPresent) { - return result - } - - return ops.getNumberValue(input).flatMap { - DataResult.success(Pair(Decimal(it.toString()), ops.empty())) - } + }.get().map( + { + DataResult.success(it) + }, + { e0 -> + ops.getIntStream(input).flatMap { + try { + DataResult.success(Pair(Decimal.fromByteArray( + it.mapToObj { val v = it.toLong(); Stream.of(v and 0xFFL, (v and 0xFF00L) ushr 8, (v and 0xFF0000L) ushr 16, (v and 0xFF000000L) ushr 24) } + .flatMap { it } + .collect(::ByteArrayList, { v, a -> v.add(a.toByte()) }, ByteArrayList::addAll) + .toByteArray() + ), ops.empty())) + } catch (err: NumberFormatException) { + DataResult.error { "Failed to convert array of bytes into Decimal: $it" } + } + }.get().map( + { + DataResult.success(it) + }, + { e1 -> + ops.getNumberValue(input).flatMap { + DataResult.success(Pair(Decimal(it.toString()), ops.empty())) + }.get().map( + { + DataResult.success(it) + }, + { e2 -> + DataResult.error { "None of attempts at decoding Decimal were successful: ${e0.message()}; ${e1.message()}; ${e2.message()}" } + } + ) + } + ) + } + ) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt index fcc744d47..da9e8fde5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Ext.kt @@ -3,12 +3,17 @@ package ru.dbotthepony.mc.otm.data import com.google.gson.JsonArray import com.google.gson.JsonNull import com.google.gson.JsonObject +import com.mojang.datafixers.kinds.App +import com.mojang.serialization.Codec import com.mojang.serialization.DataResult +import com.mojang.serialization.codecs.RecordCodecBuilder import it.unimi.dsi.fastutil.ints.IntAVLTreeSet import net.minecraft.world.entity.player.Player import net.minecraft.world.level.storage.loot.LootContext import net.minecraft.world.level.storage.loot.parameters.LootContextParam import net.minecraft.world.level.storage.loot.parameters.LootContextParams +import java.util.Optional +import kotlin.reflect.KProperty1 fun JsonArray.getRidOfNulls(): JsonArray { val toRemove = IntAVLTreeSet() diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt new file mode 100644 index 000000000..4accf6e75 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/UUIDCodec.kt @@ -0,0 +1,26 @@ +package ru.dbotthepony.mc.otm.data + +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import java.util.UUID +import java.util.stream.LongStream + +object UUIDCodec : Codec { + override fun encode(input: UUID, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(ops.createLongList(LongStream.of(input.mostSignificantBits, input.leastSignificantBits))) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return ops.getLongStream(input).flatMap { + val l = it.limit(2).toArray() + + if (l.size != 2) { + DataResult.error { "Can't construct UUID from ${l.size} elements" } + } else { + DataResult.success(Pair(UUID(l[0], l[1]), ops.empty())) + } + } + } +}