Unify experience storage in machines, Liquid XP

This commit is contained in:
DBotThePony 2023-12-22 20:08:12 +07:00
parent 8fece2a517
commit 1e2c505fbd
Signed by: DBot
GPG Key ID: DCC23B5715498507
22 changed files with 388 additions and 156 deletions

View File

@ -21,7 +21,8 @@ fun addMatterEntanglerRecipes(consumer: RecipeOutput) {
),
Decimal(40),
400.0,
ItemStack(MItems.QUANTUM_CAPACITOR, 2)
ItemStack(MItems.QUANTUM_CAPACITOR, 2),
experience = 15f
).energetic().toFinished(modLocation("quantum_capacitor"))
)
@ -34,7 +35,8 @@ fun addMatterEntanglerRecipes(consumer: RecipeOutput) {
),
Decimal(120),
600.0,
ItemStack(MItems.QUANTUM_BATTERY, 2)
ItemStack(MItems.QUANTUM_BATTERY, 2),
experience = 20f
).energetic().toFinished(modLocation("quantum_battery"))
)
}

View File

@ -23,7 +23,6 @@ import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.VoxelShape
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.block.entity.IRedstoneControlled
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
@ -60,9 +59,20 @@ fun Block.getShapeForEachState(property: Property<*>, fn: (BlockState) -> VoxelS
return getShapeForEachState(listOf(property), fn)
}
fun interface INeighbourChangeListener {
fun neighborChanged(
state: BlockState,
level: Level,
pos: BlockPos,
neighbour: Block,
neighbourPos: BlockPos,
movedByPiston: Boolean
)
}
abstract class MatteryBlock @JvmOverloads constructor(
properties: Properties = DEFAULT_PROPERTIES
) : Block(properties) {
) : Block(properties), INeighbourChangeListener {
override fun setPlacedBy(
level: Level,
blockPos: BlockPos,
@ -168,7 +178,6 @@ abstract class MatteryBlock @JvmOverloads constructor(
return null
}
@Suppress("OVERRIDE_DEPRECATION")
override fun neighborChanged(
state: BlockState,
level: Level,
@ -188,8 +197,8 @@ abstract class MatteryBlock @JvmOverloads constructor(
if (tile is IRedstoneControlled)
tile.redstoneControl.redstoneSignal = level.getBestNeighborSignal(pos)
if (tile is MatteryBlockEntity && SERVER_IS_LIVE)
tile.neighborChanged(neighbour, neighbourPos, movedByPiston)
if (tile is MatteryBlockEntity)
tile.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston)
}
}
}

View File

@ -0,0 +1,154 @@
package ru.dbotthepony.mc.otm.block.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.nbt.DoubleTag
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.ExperienceOrb
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.block.INeighbourChangeListener
import ru.dbotthepony.mc.otm.block.entity.tech.EssenceStorageBlockEntity
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.registry.MFluids
import java.util.function.DoubleSupplier
class ExperienceStorage(val maxExperience: DoubleSupplier = DoubleSupplier { Double.POSITIVE_INFINITY }) : IFluidHandler, INBTSerializable<DoubleTag?>, INeighbourChangeListener {
constructor(max: Double) : this({ max })
var experience = 0.0
private set(value) {
require(value >= 0.0) { "Negative experience: $value" }
field = value
}
fun popExperience(player: ServerPlayer) {
val whole = experience.toInt()
if (whole > 0) {
experience -= whole
ExperienceOrb.award(player.level() as ServerLevel, player.position(), whole)
}
}
fun storeExperience(experience: Double): Boolean {
check(experience >= 0.0) { "Invalid experience amount to store: $experience" }
val max = maxExperience.asDouble
val overflow = this.experience + experience > max
this.experience = if (overflow) max else this.experience + experience
return !overflow
}
fun storeExperience(experience: Float): Boolean {
return storeExperience(experience.toDouble())
}
fun storeExperience(experience: Double, pointOfReference: BlockEntity): Boolean {
check(experience >= 0.0) { "Invalid experience amount to store: $experience" }
this.experience += experience
for (dir in Direction.entries) {
val tile = pointOfReference.level?.getBlockEntity(pointOfReference.blockPos + dir)
if (tile is EssenceStorageBlockEntity) {
tile.experienceStored += this.experience.toLong()
this.experience %= 1.0
break
}
}
val max = maxExperience.asDouble
val overflow = this.experience > max
if (overflow) this.experience = max
return !overflow
}
fun storeExperience(experience: Float, pointOfReference: BlockEntity): Boolean {
return storeExperience(experience.toDouble(), pointOfReference)
}
fun tryTransferExperience(pointOfReference: BlockEntity) {
if (experience >= 1.0) {
for (dir in Direction.entries) {
val tile = pointOfReference.level?.getBlockEntity(pointOfReference.blockPos + dir)
if (tile is EssenceStorageBlockEntity) {
tile.experienceStored += experience.toLong()
experience %= 1.0
break
}
}
}
}
override fun neighborChanged(state: BlockState, level: Level, pos: BlockPos, neighbour: Block, neighbourPos: BlockPos, movedByPiston: Boolean) {
if (experience >= 1.0) {
val tile = level.getBlockEntity(neighbourPos)
if (tile is EssenceStorageBlockEntity) {
tile.experienceStored += experience.toLong()
experience %= 1.0
}
}
}
override fun serializeNBT(): DoubleTag {
return DoubleTag.valueOf(experience)
}
override fun deserializeNBT(nbt: DoubleTag?) {
experience = (nbt?.asDouble ?: 0.0).coerceAtLeast(0.0)
}
override fun getTanks(): Int {
return 1
}
override fun getFluidInTank(tank: Int): FluidStack {
if (tank != 0)
return FluidStack.EMPTY
return FluidStack(MFluids.LIQUID_XP, (experience * XP_TO_LIQUID_RATIO).toInt())
}
override fun getTankCapacity(tank: Int): Int {
return if (tank == 0) (maxExperience.asDouble * XP_TO_LIQUID_RATIO).toInt() else 0
}
override fun isFluidValid(tank: Int, stack: FluidStack): Boolean {
return tank == 0 && stack.fluid.fluidType == MFluids.LIQUID_XP_TYPE
}
override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int {
return 0
}
override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack {
if (resource.fluid.fluidType != MFluids.LIQUID_XP_TYPE)
return FluidStack.EMPTY
return drain(resource.amount, action)
}
override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack {
val actualDrain = maxDrain.coerceAtMost((experience * XP_TO_LIQUID_RATIO).toInt()).let { it / XP_TO_LIQUID_RATIO * XP_TO_LIQUID_RATIO }
if (actualDrain <= 0)
return FluidStack.EMPTY
if (action.execute())
experience -= actualDrain / XP_TO_LIQUID_RATIO
return FluidStack(MFluids.LIQUID_XP, actualDrain)
}
companion object {
const val XP_TO_LIQUID_RATIO = 10
}
}

View File

@ -12,7 +12,6 @@ import net.minecraft.core.Direction
import net.minecraft.core.SectionPos
import net.minecraft.core.Vec3i
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
@ -28,7 +27,6 @@ import net.minecraft.world.level.chunk.LevelChunk
import net.minecraft.world.phys.Vec3
import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.common.util.LazyOptional
import net.minecraftforge.energy.IEnergyStorage
import net.minecraftforge.event.TickEvent.LevelTickEvent
@ -37,6 +35,7 @@ import net.minecraftforge.event.level.ChunkWatchEvent
import net.minecraftforge.event.level.LevelEvent
import net.minecraftforge.event.server.ServerStoppingEvent
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.block.INeighbourChangeListener
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
@ -65,12 +64,11 @@ import java.util.function.Supplier
import java.util.stream.Stream
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
/**
* Absolute barebone (lol) block entity class in Overdrive that Matters, providing bare minimum (lulmao, minecraft engine) functionality
*/
abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(p_155228_, p_155229_, p_155230_) {
abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(p_155228_, p_155229_, p_155230_), INeighbourChangeListener {
private var isSynchronizing = false
/**
@ -83,12 +81,21 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
}
private val _droppableContainers = ObjectArraySet<Container>()
private val _neighbourChangeListeners = ObjectArraySet<INeighbourChangeListener>()
val droppableContainers: Set<Container> = Collections.unmodifiableSet(_droppableContainers)
val neighbourChangeListeners: Set<INeighbourChangeListener> = Collections.unmodifiableSet(_neighbourChangeListeners)
protected fun addDroppableContainer(container: Container) {
_droppableContainers.add(container)
}
protected fun addNeighbourListener(listener: INeighbourChangeListener) {
if (listener === this)
throw IllegalArgumentException("are you drunk")
_neighbourChangeListeners.add(listener)
}
open fun beforeDroppingItems(oldBlockState: BlockState, level: Level, blockPos: BlockPos, newBlockState: BlockState, movedByPiston: Boolean) {}
open fun beforeDestroyedByPlayer(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player) {}
@ -441,7 +448,8 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
}
}
fun neighborChanged(neighbour: Block, neighbourPos: BlockPos, movedByPiston: Boolean) {
override fun neighborChanged(state: BlockState, level: Level, pos: BlockPos, neighbour: Block, neighbourPos: BlockPos, movedByPiston: Boolean) {
_neighbourChangeListeners.forEach { it.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) }
val dir = vec2Dir[vecKey(neighbourPos - blockPos)] ?: return
_sides[blockRotation.dir2Side(dir)]!!.updateTracked()
}

View File

@ -9,6 +9,8 @@ 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.capabilities.ForgeCapabilities
import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage
import ru.dbotthepony.mc.otm.block.entity.ItemJob
import ru.dbotthepony.mc.otm.block.entity.JobContainer
import ru.dbotthepony.mc.otm.block.entity.JobStatus
@ -35,7 +37,7 @@ import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MRecipes
class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryWorkerBlockEntity<MatterEntanglerBlockEntity.Job>(MBlockEntities.MATTER_ENTANGLER, blockPos, blockState, Job.CODEC) {
class Job(itemStack: ItemStack, val matter: Decimal, ticks: Double) : ItemJob(itemStack, ticks, MachinesConfig.MATTER_ENTANGLER.energyConsumption) {
class Job(itemStack: ItemStack, val matter: Decimal, ticks: Double, experience: Float) : ItemJob(itemStack, ticks, MachinesConfig.MATTER_ENTANGLER.energyConsumption, experience = experience) {
val matterPerTick = matter / ticks
companion object {
@ -44,6 +46,7 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
ItemStack.CODEC.fieldOf("itemStack").forGetter(ItemJob::itemStack),
DecimalCodec.minRange(Decimal.ZERO).fieldOf("matter").forGetter(Job::matter),
Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(ItemJob::ticks),
Codec.FLOAT.minRange(0f).optionalFieldOf("experience", 0f).forGetter(Job::experience)
).apply(it, ::Job)
}
}
@ -54,6 +57,7 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
val matter = ProfiledMatterStorage(MatterStorageImpl(::markDirtyFast, FlowDirection.INPUT, upgrades.matterCapacity(MachinesConfig.MATTER_ENTANGLER::matterCapacity)))
val node = MatterNode()
val experience = ExperienceStorage(MachinesConfig.MATTER_ENTANGLER::maxExperienceStored).also(::addNeighbourListener)
val energyConfig = ConfigurableEnergy(energy)
val inputs = object : MatteryCraftingContainer(::itemContainerUpdated, 3, 3) {
@ -88,11 +92,14 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
)
init {
exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience)
savetables.stateful(::energy, ENERGY_KEY)
savetables.stateful(::matter, MATTER_STORAGE_KEY)
savetables.stateful(::upgrades)
savetables.stateful(::inputs)
savetables.stateful(::output)
savetables.stateful(::experience)
exposeGlobally(MatteryCapability.MATTER_NODE, node)
exposeGlobally(MatteryCapability.MATTER, matter)
@ -128,6 +135,8 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
override fun onJobFinish(status: JobStatus<Job>, id: Int) {
if (!output.fullyAddItem(status.job.itemStack)) {
status.noItem()
} else {
experience.storeExperience(status.job.experience, this)
}
}
@ -150,7 +159,8 @@ class MatterEntanglerBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
Job(
result,
recipe.value.matter,
recipe.value.ticks * MachinesConfig.MATTER_ENTANGLER.workTimeMultiplier
recipe.value.ticks * MachinesConfig.MATTER_ENTANGLER.workTimeMultiplier,
recipe.value.experience
)
)
}

View File

@ -7,6 +7,9 @@ import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage.Companion.XP_TO_LIQUID_RATIO
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler
import ru.dbotthepony.mc.otm.container.HandlerFilter
@ -14,8 +17,9 @@ import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.item.EssenceCapsuleItem
import ru.dbotthepony.mc.otm.menu.tech.EssenceStorageMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MFluids
class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ESSENCE_STORAGE, blockPos, blockState) {
class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.ESSENCE_STORAGE, blockPos, blockState), IFluidHandler {
var experienceStored = 0L
set(value) {
require(value >= 0L) { "Negative experience: $value" }
@ -57,6 +61,62 @@ class EssenceStorageBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
)
)
override fun getTanks(): Int {
return 1
}
override fun getFluidInTank(tank: Int): FluidStack {
if (tank != 0)
return FluidStack.EMPTY
if (experienceStored >= 2_000_000_000L)
return FluidStack(MFluids.LIQUID_XP, 2_000_000_000)
return FluidStack(MFluids.LIQUID_XP, experienceStored.toInt())
}
override fun getTankCapacity(tank: Int): Int {
if (tank != 0) return 0
return Int.MAX_VALUE
}
override fun isFluidValid(tank: Int, stack: FluidStack): Boolean {
return tank == 0 && stack.fluid.fluidType == MFluids.LIQUID_XP_TYPE
}
override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int {
if (resource.fluid.fluidType != MFluids.LIQUID_XP_TYPE || resource.amount <= 0)
return 0
val actualFill = resource.amount / XP_TO_LIQUID_RATIO * XP_TO_LIQUID_RATIO
if (action.execute())
experienceStored += actualFill / XP_TO_LIQUID_RATIO
return actualFill
}
override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack {
if (resource.fluid.fluidType != MFluids.LIQUID_XP_TYPE)
return FluidStack.EMPTY
return drain(resource.amount, action)
}
override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack {
val stored = experienceStored * XP_TO_LIQUID_RATIO
val actualStored = if (stored >= Int.MAX_VALUE) Int.MAX_VALUE else stored.toInt()
val actualDrain = maxDrain.coerceAtMost(actualStored).let { it / XP_TO_LIQUID_RATIO * XP_TO_LIQUID_RATIO }
if (actualDrain <= 0)
return FluidStack.EMPTY
if (action.execute())
experienceStored -= actualDrain / XP_TO_LIQUID_RATIO
return FluidStack(MFluids.LIQUID_XP, actualDrain)
}
override fun tick() {
super.tick()

View File

@ -2,14 +2,12 @@ package ru.dbotthepony.mc.otm.block.entity.tech
import it.unimi.dsi.fastutil.ints.IntArrayList
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
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.level.block.state.BlockState
import net.minecraftforge.common.capabilities.ForgeCapabilities
import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage
import ru.dbotthepony.mc.otm.block.entity.JobContainer
import ru.dbotthepony.mc.otm.block.entity.JobStatus
import ru.dbotthepony.mc.otm.block.entity.ItemJob
@ -39,39 +37,7 @@ class PlatePressBlockEntity(
val inputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer)
val outputContainer = MatteryContainer(this::itemContainerUpdated, if (isTwin) 2 else 1).also(::addDroppableContainer)
var experience = 0.0
private set
fun popExperience(player: ServerPlayer) {
val whole = experience.toInt()
if (whole > 0) {
experience -= whole
ExperienceOrb.award(player.level() as ServerLevel, player.position(), whole)
}
}
fun tryStoreExperience(essenceStorage: EssenceStorageBlockEntity?) {
if (experience <= 0.0) return
if (essenceStorage != null) {
essenceStorage.experienceStored += experience.toLong()
experience = 0.0
return
}
for (dir in Direction.entries) {
val tile = level?.getBlockEntity(blockPos.relative(dir)) ?: continue
if (tile is EssenceStorageBlockEntity) {
tryStoreExperience(tile)
break
}
}
}
val experience = ExperienceStorage(MachinesConfig.PLATE_PRESS::maxExperienceStored).also(::addNeighbourListener)
val energyConfig = ConfigurableEnergy(energy)
val itemConfig = ConfigurableItemHandler(
input = inputContainer.handler(HandlerFilter.OnlyIn),
@ -79,10 +45,12 @@ class PlatePressBlockEntity(
)
init {
exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience)
savetables.stateful(::energy, ENERGY_KEY)
savetables.stateful(::inputContainer)
savetables.stateful(::outputContainer)
savetables.double(::experience)
savetables.stateful(::experience)
savetables.stateful(::upgrades)
}
@ -100,8 +68,7 @@ class PlatePressBlockEntity(
if (!outputContainer.fullyAddItem(status.job.itemStack, slots = IntArrayList.of(id)) && !outputContainer.fullyAddItem(status.job.itemStack))
return status.noItem()
experience = (experience + status.experience).coerceAtMost(100.0)
tryStoreExperience(null)
experience.storeExperience(status.experience, this)
}
override fun computeNextJob(id: Int): JobContainer<ItemJob> {

View File

@ -12,6 +12,8 @@ import net.minecraft.world.item.crafting.AbstractCookingRecipe
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.minecraftforge.common.capabilities.ForgeCapabilities
import ru.dbotthepony.mc.otm.block.entity.ExperienceStorage
import ru.dbotthepony.mc.otm.block.entity.JobContainer
import ru.dbotthepony.mc.otm.block.entity.JobStatus
import ru.dbotthepony.mc.otm.block.entity.ItemJob
@ -53,6 +55,7 @@ class PoweredFurnaceBlockEntity(
outputs.forEach { addDroppableContainer(it) }
}
val experience = ExperienceStorage(config::maxExperienceStored).also(::addNeighbourListener)
val energyConfig = ConfigurableEnergy(energy)
val itemConfig = ConfigurableItemHandler(
input = CombinedItemHandler(inputs.map { it.handler(HandlerFilter.OnlyIn) }),
@ -60,44 +63,13 @@ class PoweredFurnaceBlockEntity(
battery = batteryItemHandler
)
var experience = 0.0
private set
fun popExperience(player: ServerPlayer) {
val whole = experience.toInt()
if (whole > 0) {
experience -= whole
ExperienceOrb.award(player.level() as ServerLevel, player.position(), whole)
}
}
fun tryStoreExperience(essenceStorage: EssenceStorageBlockEntity?) {
if (experience <= 0.0) return
if (essenceStorage != null) {
essenceStorage.experienceStored += experience.toLong()
experience = 0.0
return
}
for (dir in Direction.entries) {
val tile = level?.getBlockEntity(blockPos.relative(dir)) ?: continue
if (tile is EssenceStorageBlockEntity) {
tryStoreExperience(tile)
break
}
}
}
init {
exposeGlobally(ForgeCapabilities.FLUID_HANDLER, experience)
savetables.stateful(::upgrades)
savetables.stateful(::energy)
savetables.double(::experience)
savetables.stateful(::experience)
savetables.stateful(inputs, "input")
savetables.stateful(outputs, "output")
@ -124,8 +96,7 @@ class PoweredFurnaceBlockEntity(
override fun onJobFinish(status: JobStatus<ItemJob>, id: Int) {
if (outputs[id].fullyAddItem(status.job.itemStack)) {
experience += status.experience
tryStoreExperience(null)
experience.storeExperience(status.experience, this)
} else {
status.noItem()
}

View File

@ -66,24 +66,4 @@ class PlatePressBlock(properties: Properties = DEFAULT_PROPERTIES, val isTwin: B
): VoxelShape {
return shapes[state]!!
}
@Suppress("OVERRIDE_DEPRECATION")
override fun neighborChanged(
state: BlockState,
level: Level,
pos: BlockPos,
neighbour: Block,
neighbourPos: BlockPos,
movedByPiston: Boolean
) {
super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston)
val tile = level.getBlockEntity(pos) ?: return
val neighbourTile = level.getBlockEntity(neighbourPos) ?: return
if (tile.isRemoved || neighbourTile.isRemoved) return
if (tile is PlatePressBlockEntity && neighbourTile is EssenceStorageBlockEntity) {
tile.tryStoreExperience(neighbourTile)
}
}
}

View File

@ -61,24 +61,4 @@ class PoweredFurnaceBlock(
return BlockEntityTicker { _, _, _, tile -> if (tile is PoweredFurnaceBlockEntity) tile.tick() }
}
@Suppress("OVERRIDE_DEPRECATION")
override fun neighborChanged(
state: BlockState,
level: Level,
pos: BlockPos,
neighbour: Block,
neighbourPos: BlockPos,
movedByPiston: Boolean
) {
super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston)
val tile = level.getBlockEntity(pos) ?: return
val neighbourTile = level.getBlockEntity(neighbourPos) ?: return
if (tile.isRemoved || neighbourTile.isRemoved) return
if (tile is PoweredFurnaceBlockEntity && neighbourTile is EssenceStorageBlockEntity) {
tile.tryStoreExperience(neighbourTile)
}
}
}

View File

@ -51,6 +51,7 @@ abstract class AbstractConfig(private val configName: String, private val type:
workTimeMultiplier: Double? = 1.0,
energyConsumption: Decimal?,
matterCapacity: Decimal? = null,
maxExperience: Double? = null,
configurator: ForgeConfigSpec.Builder.() -> Unit = {}
): WorkerBalanceValues {
builder.push(name)
@ -61,6 +62,7 @@ abstract class AbstractConfig(private val configName: String, private val type:
override val energyConsumption: Decimal by (if (energyConsumption == null) GetterSetter.box(Decimal.ZERO) else builder.defineDecimal("ENERGY_CONSUMPTION", energyConsumption, minimum = Decimal.ONE))
override val matterCapacity: Decimal by (if (matterCapacity == null) GetterSetter.box(Decimal.ZERO) else builder.defineDecimal("MATTER_CAPACITY", matterCapacity, minimum = Decimal.ONE))
override val workTimeMultiplier: Double by (if (workTimeMultiplier == null) GetterSetter.box(1.0) else builder.defineInRange("WORK_TIME_MULTIPLIER", workTimeMultiplier, 0.0))
override val maxExperienceStored: Double by (if (maxExperience == null) GetterSetter.box(Double.POSITIVE_INFINITY) else builder.defineInRange("MAX_EXPERIENCE_STORED", maxExperience, 0.0))
}
configurator.invoke(builder)

View File

@ -11,6 +11,7 @@ interface WorkerBalanceValues : EnergyBalanceValues {
val workTimeMultiplier: Double
val energyConsumption: Decimal
val matterCapacity: Decimal
val maxExperienceStored: Double
}
interface VerboseEnergyBalanceValues {

View File

@ -10,7 +10,8 @@ object MachinesConfig : AbstractConfig("machines") {
MNames.PLATE_PRESS,
energyStorage = Decimal(40_000),
energyThroughput = Decimal(200),
energyConsumption = Decimal(15)
energyConsumption = Decimal(15),
maxExperience = 200.0,
)
val MATTER_ENTANGLER = workerValues(
@ -19,6 +20,7 @@ object MachinesConfig : AbstractConfig("machines") {
energyThroughput = Decimal(400),
energyConsumption = Decimal(120),
matterCapacity = Decimal(60),
maxExperience = 400.0,
)
val MATTER_DECOMPOSER = workerValues(
@ -169,7 +171,8 @@ object MachinesConfig : AbstractConfig("machines") {
energyStorage = Decimal(40_000),
energyThroughput = Decimal(200),
energyConsumption = Decimal(20),
workTimeMultiplier = 0.75
workTimeMultiplier = 0.75,
maxExperience = 200.0
)
val POWERED_BLAST_FURNACE = workerValues(
@ -177,7 +180,8 @@ object MachinesConfig : AbstractConfig("machines") {
energyStorage = Decimal(40_000),
energyThroughput = Decimal(200),
energyConsumption = Decimal(20),
workTimeMultiplier = 0.75
workTimeMultiplier = 0.75,
maxExperience = 200.0
)
val POWERED_SMOKER = workerValues(
@ -185,7 +189,8 @@ object MachinesConfig : AbstractConfig("machines") {
energyStorage = Decimal(40_000),
energyThroughput = Decimal(200),
energyConsumption = Decimal(10),
workTimeMultiplier = 0.75
workTimeMultiplier = 0.75,
maxExperience = 200.0
)
object Upgrades {

View File

@ -57,7 +57,7 @@ class MatterEntanglerMenu(
}
}
val outputs = makeSlots(tile?.output ?: SimpleContainer(1), ::OutputSlot)
val outputs = makeSlots(tile?.output ?: SimpleContainer(1)) { a, b -> OutputSlot(a, b) { tile?.experience?.popExperience(player as ServerPlayer) } }
val upgrades = makeUpgradeSlots(3, tile?.upgrades)
private val entangling: Container = if (tile == null) object : SimpleContainer(2) {

View File

@ -13,13 +13,13 @@ import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget
import ru.dbotthepony.mc.otm.registry.MMenus
class PlatePressMenu @JvmOverloads constructor(
class PlatePressMenu(
containerID: Int,
inventory: Inventory,
tile: PlatePressBlockEntity? = null
) : MatteryPoweredMenu(MMenus.PLATE_PRESS, containerID, inventory, tile) {
val inputSlot = MatterySlot(tile?.inputContainer ?: SimpleContainer(1), 0)
val outputSlot = OutputSlot(tile?.outputContainer ?: SimpleContainer(1), 0) { tile?.popExperience(player as ServerPlayer) }
val outputSlot = OutputSlot(tile?.outputContainer ?: SimpleContainer(1), 0) { tile?.experience?.popExperience(player as ServerPlayer) }
val progressGauge = ProgressGaugeWidget(this, tile)
val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true)

View File

@ -23,7 +23,7 @@ class PoweredFurnaceMenu(
tile: PoweredFurnaceBlockEntity? = null
) : MatteryPoweredMenu(type, containerID, inventory, tile) {
val inputSlots = makeSlots(tile?.inputs, 2, ::MatterySlot)
val outputSlots = makeSlots(tile?.outputs, 2) { c, s -> OutputSlot(c, s) { tile?.popExperience(player as ServerPlayer) } }
val outputSlots = makeSlots(tile?.outputs, 2) { c, s -> OutputSlot(c, s) { tile?.experience?.popExperience(player as ServerPlayer) } }
val progressGauge = immutableList(2) { ProgressGaugeWidget(this, tile?.jobEventLoops?.get(it)) }
val itemConfig = ItemConfigPlayerInput(this, tile?.itemConfig, allowPush = true)

View File

@ -15,13 +15,13 @@ import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget
import ru.dbotthepony.mc.otm.registry.MMenus
class TwinPlatePressMenu @JvmOverloads constructor(
class TwinPlatePressMenu(
containerID: Int,
inventory: Inventory,
tile: PlatePressBlockEntity? = null
) : MatteryPoweredMenu(MMenus.TWIN_PLATE_PRESS, containerID, inventory, tile) {
val inputSlots = makeSlots(tile?.inputContainer ?: SimpleContainer(2), ::MatterySlot)
val outputSlots = makeSlots(tile?.outputContainer ?: SimpleContainer(2)) { a, b -> OutputSlot(a, b) { tile?.popExperience(player as ServerPlayer) } }
val outputSlots = makeSlots(tile?.outputContainer ?: SimpleContainer(2)) { a, b -> OutputSlot(a, b) { tile?.experience?.popExperience(player as ServerPlayer) } }
val progressGauge0 = ProgressGaugeWidget(this, tile?.jobEventLoops?.get(0))
val progressGauge1 = ProgressGaugeWidget(this, tile?.jobEventLoops?.get(1))

View File

@ -36,6 +36,7 @@ interface IMatterEntanglerRecipe : IMatteryRecipe<CraftingContainer> {
val ticks: Double
val ingredients: IIngredientMatrix
val result: ItemStack
val experience: Float
fun preemptivelyMatches(container: CraftingContainer, level: Level): Boolean
}
@ -45,6 +46,7 @@ open class MatterEntanglerRecipe(
override val matter: Decimal,
override val ticks: Double,
override val result: ItemStack,
override val experience: Float = 0f,
val uuidKey: String = "uuid",
val fixedUuid: Optional<UUID> = Optional.empty()
) : IMatterEntanglerRecipe {
@ -147,6 +149,7 @@ open class MatterEntanglerRecipe(
DecimalCodec.minRange(Decimal.ZERO).fieldOf("matter").forGetter(MatterEntanglerRecipe::matter),
Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(MatterEntanglerRecipe::ticks),
ItemStack.CODEC.fieldOf("result").forGetter(MatterEntanglerRecipe::result),
Codec.FLOAT.minRange(0f).optionalFieldOf("experience", 0f).forGetter(MatterEntanglerRecipe::experience),
Codec.STRING.optionalFieldOf("uuidKey", "uuid").forGetter(MatterEntanglerRecipe::uuidKey),
UUIDUtil.STRING_CODEC.optionalFieldOf("fixedUuid").forGetter(MatterEntanglerRecipe::fixedUuid)
).apply(it, ::MatterEntanglerRecipe)

View File

@ -12,7 +12,17 @@ import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.block.*
import net.minecraft.world.level.block.AnvilBlock
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.DoorBlock
import net.minecraft.world.level.block.DropExperienceBlock
import net.minecraft.world.level.block.IronBarsBlock
import net.minecraft.world.level.block.LiquidBlock
import net.minecraft.world.level.block.SlabBlock
import net.minecraft.world.level.block.SoundType
import net.minecraft.world.level.block.StairBlock
import net.minecraft.world.level.block.TrapDoorBlock
import net.minecraft.world.level.block.WallBlock
import net.minecraft.world.level.block.state.BlockBehaviour
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.properties.BlockSetType
@ -23,32 +33,25 @@ import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.registries.DeferredRegister
import net.minecraftforge.registries.ForgeRegistries
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.block.tech.AndroidStationBlock
import ru.dbotthepony.mc.otm.block.tech.BatteryBankBlock
import ru.dbotthepony.mc.otm.block.BlackHoleBlock
import ru.dbotthepony.mc.otm.block.BlockExplosionDebugger
import ru.dbotthepony.mc.otm.block.tech.BlockGravitationStabilizer
import ru.dbotthepony.mc.otm.block.tech.BlockGravitationStabilizerLens
import ru.dbotthepony.mc.otm.block.BlockSphereDebugger
import ru.dbotthepony.mc.otm.block.tech.ChemicalGeneratorBlock
import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock
import ru.dbotthepony.mc.otm.block.tech.EnergyServoBlock
import ru.dbotthepony.mc.otm.block.decorative.LaboratoryLamp
import ru.dbotthepony.mc.otm.block.decorative.LaboratoryLampLight
import ru.dbotthepony.mc.otm.block.MatterCableBlock
import ru.dbotthepony.mc.otm.block.tech.PhantomAttractorBlock
import ru.dbotthepony.mc.otm.block.tech.PlatePressBlock
import ru.dbotthepony.mc.otm.block.StorageCableBlock
import ru.dbotthepony.mc.otm.block.decorative.DevChestBlock
import ru.dbotthepony.mc.otm.block.decorative.EngineBlock
import ru.dbotthepony.mc.otm.block.decorative.FluidTankBlock
import ru.dbotthepony.mc.otm.block.decorative.HoloSignBlock
import ru.dbotthepony.mc.otm.block.decorative.InfiniteWaterSourceBlock
import ru.dbotthepony.mc.otm.block.matter.MatterReconstructorBlock
import ru.dbotthepony.mc.otm.block.decorative.LaboratoryLamp
import ru.dbotthepony.mc.otm.block.decorative.LaboratoryLampLight
import ru.dbotthepony.mc.otm.block.decorative.PainterBlock
import ru.dbotthepony.mc.otm.block.matter.MatterBottlerBlock
import ru.dbotthepony.mc.otm.block.matter.MatterCapacitorBankBlock
import ru.dbotthepony.mc.otm.block.matter.MatterDecomposerBlock
import ru.dbotthepony.mc.otm.block.matter.MatterEntanglerBlock
import ru.dbotthepony.mc.otm.block.matter.MatterPanelBlock
import ru.dbotthepony.mc.otm.block.matter.MatterReconstructorBlock
import ru.dbotthepony.mc.otm.block.matter.MatterRecyclerBlock
import ru.dbotthepony.mc.otm.block.matter.MatterReplicatorBlock
import ru.dbotthepony.mc.otm.block.matter.MatterScannerBlock
@ -61,10 +64,17 @@ import ru.dbotthepony.mc.otm.block.storage.StorageExporterBlock
import ru.dbotthepony.mc.otm.block.storage.StorageImporterBlock
import ru.dbotthepony.mc.otm.block.storage.StoragePowerSupplierBlock
import ru.dbotthepony.mc.otm.block.tech.AndroidChargerBlock
import ru.dbotthepony.mc.otm.block.tech.AndroidStationBlock
import ru.dbotthepony.mc.otm.block.tech.BatteryBankBlock
import ru.dbotthepony.mc.otm.block.tech.BlockGravitationStabilizer
import ru.dbotthepony.mc.otm.block.tech.BlockGravitationStabilizerLens
import ru.dbotthepony.mc.otm.block.tech.ChemicalGeneratorBlock
import ru.dbotthepony.mc.otm.block.tech.CobblerBlock
import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock
import ru.dbotthepony.mc.otm.block.tech.EnergyServoBlock
import ru.dbotthepony.mc.otm.block.tech.EssenceStorageBlock
import ru.dbotthepony.mc.otm.block.decorative.PainterBlock
import ru.dbotthepony.mc.otm.block.matter.MatterEntanglerBlock
import ru.dbotthepony.mc.otm.block.tech.PhantomAttractorBlock
import ru.dbotthepony.mc.otm.block.tech.PlatePressBlock
import ru.dbotthepony.mc.otm.block.tech.PoweredFurnaceBlock
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.TranslatableComponent
@ -126,6 +136,8 @@ object MBlocks {
val FLUID_TANK: FluidTankBlock by registry.register(MNames.FLUID_TANK) { FluidTankBlock() }
val DEV_CHEST: DevChestBlock by registry.register(MNames.DEV_CHEST) { DevChestBlock() }
val LIQUID_XP: LiquidBlock by registry.register("liquid_xp") { LiquidBlock(MFluids::LIQUID_XP, BlockBehaviour.Properties.of().mapColor(MapColor.EMERALD).replaceable().noCollission().strength(100.0f).pushReaction(PushReaction.DESTROY).noLootTable().liquid().sound(SoundType.EMPTY)) }
val TRITANIUM_ORE: Block by registry.register(MNames.TRITANIUM_ORE) { DropExperienceBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.STONE)

View File

@ -0,0 +1,66 @@
package ru.dbotthepony.mc.otm.registry
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.item.Rarity
import net.minecraft.world.level.material.Fluid
import net.minecraftforge.client.extensions.common.IClientFluidTypeExtensions
import net.minecraftforge.common.SoundActions
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.fluids.FluidType
import net.minecraftforge.fluids.ForgeFlowingFluid
import net.minecraftforge.registries.DeferredRegister
import net.minecraftforge.registries.ForgeRegistries
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import java.util.function.Consumer
object MFluids {
private val types: DeferredRegister<FluidType> = DeferredRegister.create(ForgeRegistries.FLUID_TYPES, OverdriveThatMatters.MOD_ID)
private val fluids: DeferredRegister<Fluid> = DeferredRegister.create(ForgeRegistries.FLUIDS, OverdriveThatMatters.MOD_ID)
internal fun register(bus: IEventBus) {
types.register(bus)
fluids.register(bus)
}
private fun makeXpProps(): ForgeFlowingFluid.Properties {
return ForgeFlowingFluid.Properties(::LIQUID_XP_TYPE, ::LIQUID_XP, ::LIQUID_XP_FLOWING).bucket(MItems::LIQUID_XP_BUCKET).block(MBlocks::LIQUID_XP)
}
private val xpProps = makeXpProps()
val LIQUID_XP_TYPE: FluidType by types.register("liquid_xp") {
object : FluidType(
Properties.create()
.rarity(Rarity.UNCOMMON)
.canDrown(true)
.canExtinguish(false)
.canSwim(true)
.canHydrate(false)
.canPushEntity(true)
.canConvertToSource(false)
.fallDistanceModifier(0f)
.sound(SoundActions.BUCKET_FILL, SoundEvents.BUCKET_FILL)
.sound(SoundActions.BUCKET_EMPTY, SoundEvents.BUCKET_EMPTY)
.sound(SoundActions.FLUID_VAPORIZE, SoundEvents.FIRE_EXTINGUISH)
.descriptionId("block.overdrive_that_matters.liquid_xp")
) {
override fun initializeClient(consumer: Consumer<IClientFluidTypeExtensions>) {
val path = ResourceLocation(OverdriveThatMatters.MOD_ID, "block/ph_kitty")
consumer.accept(object : IClientFluidTypeExtensions {
override fun getStillTexture(): ResourceLocation {
return path
}
override fun getFlowingTexture(): ResourceLocation {
return path
}
})
}
}
}
val LIQUID_XP: ForgeFlowingFluid.Source by fluids.register("liquid_xp") { ForgeFlowingFluid.Source(xpProps) }
val LIQUID_XP_FLOWING: ForgeFlowingFluid.Flowing by fluids.register("liquid_xp_flowing") { ForgeFlowingFluid.Flowing(xpProps) }
}

View File

@ -290,6 +290,7 @@ object MItems {
val FLUID_CAPSULE: FluidCapsuleItem by registry.register("fluid_capsule") { FluidCapsuleItem(ItemsConfig::FLUID_CAPSULE_CAPACITY) }
val FLUID_TANK: FluidTankItem by registry.register(MNames.FLUID_TANK) { FluidTankItem(MBlocks.FLUID_TANK, Item.Properties().stacksTo(1), ItemsConfig::FLUID_TANK_CAPACITY) }
val LIQUID_XP_BUCKET: BucketItem by registry.register("liquid_xp_bucket") { BucketItem(MFluids::LIQUID_XP, Item.Properties().stacksTo(1).rarity(Rarity.UNCOMMON)) }
val TRITANIUM_COMPONENT: ForgeTier = ForgeTier(
Tiers.IRON.level,

View File

@ -275,6 +275,7 @@ object MRegistry {
IMatterFunction.register(bus)
MBlocks.register(bus)
MFluids.register(bus)
MBlockEntities.register(bus)
MEntityTypes.register(bus)
MMenus.register(bus)