Compare commits

...

3 Commits

12 changed files with 373 additions and 60 deletions

View File

@ -14,6 +14,7 @@ import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.ComponentSerialization
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
@ -259,15 +260,11 @@ open class MatteryBlock(properties: Properties = DEFAULT_PROPERTIES) : Block(pro
newBlockState: BlockState,
movedByPiston: Boolean
) {
if (!oldBlockState.`is`(newBlockState.block) && !level.isClientSide) {
if (!oldBlockState.`is`(newBlockState.block) && level is ServerLevel) {
val blockentity = level.getBlockEntity(blockPos)
if (blockentity is MatteryBlockEntity) {
blockentity.beforeDroppingItems(oldBlockState, level, blockPos, newBlockState, movedByPiston)
for (container in blockentity.droppableContainers)
Containers.dropContents(level, blockPos, container)
blockentity.dropItems(oldBlockState, level, blockPos, newBlockState, movedByPiston)
level.updateNeighbourForOutputSignal(blockPos, this)
}
}

View File

@ -0,0 +1,26 @@
package ru.dbotthepony.mc.otm.block.decorative
import net.minecraft.core.BlockPos
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.block.entity.decorative.BreakableContainerBlockEntity
class BreakableContainerBlock(properties: Properties) : RotatableMatteryBlock(properties), EntityBlock {
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
return BreakableContainerBlockEntity(pos, state)
}
override fun getDrops(state: BlockState, params: LootParams.Builder): MutableList<ItemStack> {
val entity = params.getOptionalParameter(LootContextParams.BLOCK_ENTITY)
if (entity is BreakableContainerBlockEntity)
return entity.loot.getItems(params, state)
return super.getDrops(state, params)
}
}

View File

@ -17,6 +17,7 @@ import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
import net.minecraft.world.Containers
import net.minecraft.world.entity.player.Player
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level
@ -70,7 +71,7 @@ import kotlin.collections.ArrayList
/**
* 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_), INeighbourChangeListener {
abstract class MatteryBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState) : BlockEntity(type, pos, state), INeighbourChangeListener {
private val sidelessCaps = Reference2ObjectOpenHashMap<BlockCapability<*, *>, Any>()
private val sidedCaps = Array(RelativeSide.entries.size) {
Reference2ObjectOpenHashMap<BlockCapability<*, *>, ControllableCapability<*>>()
@ -121,7 +122,11 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
_neighbourChangeListeners.add(listener)
}
open fun beforeDroppingItems(oldBlockState: BlockState, level: Level, blockPos: BlockPos, newBlockState: BlockState, movedByPiston: Boolean) {}
open fun dropItems(oldBlockState: BlockState, level: ServerLevel, blockPos: BlockPos, newBlockState: BlockState, movedByPiston: Boolean) {
for (container in droppableContainers)
Containers.dropContents(level, blockPos, container)
}
open fun beforeDestroyedByPlayer(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player) {}
open fun tick() {

View File

@ -0,0 +1,23 @@
package ru.dbotthepony.mc.otm.block.entity.decorative
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.core.util.BlockLootTableHolder
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
class BreakableContainerBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(MBlockEntities.BREAKABLE, blockPos, blockState) {
val loot = BlockLootTableHolder(::markDirtyFast)
override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) {
super.saveLevel(nbt, registry)
loot.save(nbt, registry)
}
override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
super.loadAdditional(nbt, registry)
loot.load(nbt, registry)
}
}

View File

@ -1,18 +1,11 @@
package ru.dbotthepony.mc.otm.block.entity.decorative
import net.minecraft.advancements.CriteriaTriggers
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.world.Containers
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
@ -21,18 +14,14 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.LootTable
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.Vec3
import net.neoforged.neoforge.capabilities.Capabilities
import ru.dbotthepony.mc.otm.block.decorative.CargoCrateBlock
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.HandlerFilter
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.otmRandom
import ru.dbotthepony.mc.otm.core.util.BlockLootTableHolder
import ru.dbotthepony.mc.otm.menu.decorative.CargoCrateMenu
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
import ru.dbotthepony.mc.otm.registry.game.MSoundEvents
@ -47,24 +36,30 @@ class CargoCrateBlockEntity(
val handler = container.handler(object : HandlerFilter {
override fun canInsert(slot: Int, stack: ItemStack): Boolean {
return lootTable == null
return loot.lootTable == null
}
override fun canExtract(slot: Int, amount: Int, stack: ItemStack): Boolean {
return lootTable == null
return loot.lootTable == null
}
})
override fun beforeDroppingItems(oldBlockState: BlockState, level: Level, blockPos: BlockPos, newBlockState: BlockState, movedByPiston: Boolean) {
override fun dropItems(
oldBlockState: BlockState,
level: ServerLevel,
blockPos: BlockPos,
newBlockState: BlockState,
movedByPiston: Boolean
) {
unpackLootTable()
super.dropItems(oldBlockState, level, blockPos, newBlockState, movedByPiston)
}
override fun beforeDestroyedByPlayer(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player) {
unpackLootTable(player)
}
var lootTable: ResourceKey<LootTable>? = null
var lootTableSeed: Long? = null
val loot = BlockLootTableHolder(::markDirtyFast)
fun onPlayerOpen() {
val level = level
@ -92,52 +87,24 @@ class CargoCrateBlockEntity(
override fun saveLevel(nbt: CompoundTag, registry: HolderLookup.Provider) {
super.saveLevel(nbt, registry)
if (lootTable != null) {
nbt.putString(LOOT_TABLE_KEY, lootTable!!.location().toString())
nbt.putLong(LOOT_TABLE_SEED_KEY, lootTableSeed ?: 0L)
}
loot.save(nbt, registry)
}
override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
super.loadAdditional(nbt, registry)
if (nbt.contains(LOOT_TABLE_KEY, Tag.TAG_STRING.toInt())) {
lootTable = ResourceLocation.tryParse(nbt.getString(LOOT_TABLE_KEY))?.let { ResourceKey.create(Registries.LOOT_TABLE, it) }
lootTableSeed = if (nbt.contains(LOOT_TABLE_SEED_KEY, Tag.TAG_LONG.toInt())) nbt.getLong(LOOT_TABLE_SEED_KEY) else 0L
}
loot.load(nbt, registry)
}
fun unpackLootTable(ply: Player? = null) {
val lootTable = lootTable ?: return
val lootTableSeed = lootTableSeed ?: 0L
val server = level?.server ?: return
val loot = server.reloadableRegistries().getLootTable(lootTable)
if (ply is ServerPlayer) {
CriteriaTriggers.GENERATE_LOOT.trigger(ply, lootTable)
}
this.lootTable = null
this.lootTableSeed = null
val params = LootParams.Builder(level as ServerLevel)
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(this.worldPosition))
if (ply != null) {
params.withLuck(ply.luck).withParameter(LootContextParams.THIS_ENTITY, ply)
}
Containers.dropContents(level as ServerLevel, blockPos, container)
loot.fill(container, params.create(LootContextParamSets.CHEST), lootTableSeed)
private fun unpackLootTable(ply: Player? = null) {
loot.fill(level as? ServerLevel ?: return, blockPos, container, ply = ply, blockEntity = this)
loot.clear()
}
override val defaultDisplayName: Component
get() = MACHINE_NAME
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? {
if (lootTable != null && ply.isSpectator)
if (loot.lootTable != null && ply.isSpectator)
return null
unpackLootTable(ply)

View File

@ -10,6 +10,7 @@ import com.google.common.collect.ImmutableSet
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import it.unimi.dsi.fastutil.ints.IntList
import it.unimi.dsi.fastutil.objects.ObjectComparators
import net.minecraft.Util
import net.minecraft.core.BlockPos
@ -199,6 +200,15 @@ fun IntArray.shuffle(random: RandomSource): IntArray {
return this
}
fun <L : IntList> L.shuffle(random: RandomSource): L {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
set(j, set(i, getInt(j)))
}
return this
}
fun <T, L : MutableList<T>> L.shuffle(random: RandomSource): L {
Util.shuffle(this, random)
return this

View File

@ -0,0 +1,132 @@
package ru.dbotthepony.mc.otm.core.util
import net.minecraft.advancements.CriteriaTriggers
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.RandomSource
import net.minecraft.world.Container
import net.minecraft.world.Containers
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.LootTable
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.phys.Vec3
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity.Companion.LOOT_TABLE_KEY
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity.Companion.LOOT_TABLE_SEED_KEY
import ru.dbotthepony.mc.otm.core.getBlockEntityNow
import ru.dbotthepony.mc.otm.core.otmRandom
class BlockLootTableHolder(private val listener: Runnable = Runnable { }) {
private var ignoreListener = false
var lootTable: ResourceKey<LootTable>? = null
set(value) {
if (field != value) {
field = value
if (!ignoreListener) listener.run()
}
}
var lootTableSeed: Long? = null
set(value) {
if (field != value) {
field = value
if (!ignoreListener) listener.run()
}
}
fun save(nbt: CompoundTag, registry: HolderLookup.Provider) {
try {
ignoreListener = true
if (lootTable != null) {
nbt.putString(LOOT_TABLE_KEY, lootTable!!.location().toString())
if (lootTableSeed != null)
nbt.putLong(LOOT_TABLE_SEED_KEY, lootTableSeed!!)
}
} finally {
ignoreListener = false
}
}
fun load(nbt: CompoundTag, registry: HolderLookup.Provider) {
try {
ignoreListener = true
if (nbt.contains(LOOT_TABLE_KEY, Tag.TAG_STRING.toInt())) {
lootTable = ResourceLocation.tryParse(nbt.getString(LOOT_TABLE_KEY))?.let { ResourceKey.create(Registries.LOOT_TABLE, it) }
lootTableSeed = if (nbt.contains(LOOT_TABLE_SEED_KEY, Tag.TAG_LONG.toInt())) nbt.getLong(LOOT_TABLE_SEED_KEY) else null
}
} finally {
ignoreListener = false
}
}
fun lookup(level: ServerLevel): LootTable {
val lootTable = lootTable ?: return LootTable.EMPTY
return level.server.reloadableRegistries().getLootTable(lootTable)
}
private fun selectRandom(overrideSeed: Long?, fallback: RandomSource): RandomSource {
val lootTableSeed = overrideSeed ?: lootTableSeed
return if (lootTableSeed == null) fallback else GJRAND64RandomSource(lootTableSeed)
}
fun getItems(params: LootParams, level: ServerLevel, overrideSeed: Long? = null, rounds: Int = 1): MutableList<ItemStack> {
return lookup(level).getRandomItems(params, selectRandom(overrideSeed, level.otmRandom), rounds)
}
fun getItems(params: LootParams.Builder, blockState: BlockState, overrideSeed: Long? = null, rounds: Int = 1): MutableList<ItemStack> {
val level = params.level
val create = params.withParameter(LootContextParams.BLOCK_STATE, blockState).create(LootContextParamSets.BLOCK)
return getItems(create, level, overrideSeed, rounds)
}
fun fill(level: ServerLevel, blockPos: BlockPos, container: Container, ply: Player? = null, blockEntity: BlockEntity? = level.getBlockEntityNow(blockPos), overrideSeed: Long? = null, dropContents: Boolean = true, rounds: Int = 1) {
if (rounds == 0)
return
else if (rounds < 0)
throw IllegalArgumentException("Negative amount of rounds: $rounds")
val lootTable = lootTable ?: return
val server = level.server
val loot = server.reloadableRegistries().getLootTable(lootTable)
if (ply is ServerPlayer)
CriteriaTriggers.GENERATE_LOOT.trigger(ply, lootTable)
val params = LootParams.Builder(level)
.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockPos))
if (ply != null) {
params
.withLuck(ply.luck)
.withParameter(LootContextParams.THIS_ENTITY, ply)
}
if (blockEntity != null)
params.withParameter(LootContextParams.BLOCK_ENTITY, blockEntity)
if (dropContents)
Containers.dropContents(level, blockPos, container)
loot.fill(params.create(LootContextParamSets.CHEST), selectRandom(overrideSeed, level.otmRandom), container, rounds)
}
fun clear() {
lootTable = null
lootTableSeed = null
}
}

View File

@ -0,0 +1,132 @@
package ru.dbotthepony.mc.otm.core.util
import it.unimi.dsi.fastutil.ints.IntArrayList
import net.minecraft.util.Mth
import net.minecraft.util.RandomSource
import net.minecraft.world.Container
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.storage.loot.LootParams
import net.minecraft.world.level.storage.loot.LootTable
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.container.set
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.shuffle
import java.util.stream.Collectors
import java.util.stream.IntStream
import kotlin.math.min
private val LOGGER = LogManager.getLogger()
private class Bucket {
val items = ArrayList<ItemStack>()
var last = 0
fun add(item: ItemStack) {
if (item.isEmpty) return
for (i in last until items.size) {
val existing = items[i]
var available = existing.maxStackSize - existing.count
if (available > 0 && ItemStack.isSameItemSameComponents(existing, item)) {
val diff = min(available, item.count)
existing.grow(diff)
item.shrink(diff)
available -= diff
if (available == 0 && i == last)
last++
if (item.isEmpty)
return
}
}
if (item.isNotEmpty) {
items.add(item)
}
}
}
private fun recombine(lists: Collection<Collection<ItemStack>>): MutableList<ItemStack> {
val result = HashMap<Item, Bucket>()
for (list in lists)
for (item in list)
result.computeIfAbsent(item.item) { Bucket() }.add(item)
return result.values
.stream()
.flatMap { it.items.stream() }
.collect(Collectors.toCollection(::ArrayList))
}
fun LootTable.getRandomItems(params: LootParams, random: RandomSource, rounds: Int = 1): MutableList<ItemStack> {
if (rounds == 0) {
return ArrayList()
} else if (rounds == 1) {
return getRandomItems(params, random)
} else {
return recombine(IntStream.range(0, rounds).mapToObj { getRandomItems(params, random) }.toList())
}
}
private fun shuffle(items: MutableList<ItemStack>, emptySlotCount: Int, random: RandomSource) {
val pool = ArrayList<ItemStack>(items)
items.clear()
while (items.size + pool.size < emptySlotCount && pool.isNotEmpty()) {
val select = pool.removeAt(random.nextInt(pool.size))
val maxStackSize = select.maxStackSize
if (maxStackSize == 1 || select.count == 1) {
items.add(select)
} else {
val split = select.split(Mth.nextInt(random, 1, select.count / 2))
if (split.count > 1 && random.nextBoolean())
pool.add(split)
else
items.add(split)
if (select.count > 1 && random.nextBoolean())
pool.add(select)
else
items.add(select)
}
}
items.addAll(pool)
items.shuffle(random)
}
fun LootTable.fill(params: LootParams, random: RandomSource, container: Container, rounds: Int = 1) {
val emptySlots = IntArrayList()
for (i in 0 until container.containerSize)
if (container[i].isEmpty)
emptySlots.add(i)
emptySlots.shuffle(random)
if (emptySlots.isEmpty) {
LOGGER.warn("Tried to fill container with no empty slots")
return
}
val items = getRandomItems(params, random, rounds)
shuffle(items, emptySlots.size, random)
val slotItr = emptySlots.iterator()
val itemItr = items.iterator()
while (slotItr.hasNext() && itemItr.hasNext()) {
container[slotItr.nextInt()] = itemItr.next()
}
if (itemItr.hasNext()) {
LOGGER.warn("Tried to overfill a container")
}
}

View File

@ -341,6 +341,8 @@ object MNames {
const val TRITANIUM_DOOR = "tritanium_door"
const val TRITANIUM_TRAPDOOR = "tritanium_trapdoor"
const val TRITANIUM_PRESSURE_PLATE = "tritanium_pressure_plate"
const val SMALL_CAPSULE = "small_capsule"
}
object StatNames {

View File

@ -12,6 +12,7 @@ import ru.dbotthepony.mc.otm.block.entity.tech.*
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleGeneratorBlockEntity
import ru.dbotthepony.mc.otm.block.entity.cable.SimpleEnergyCableBlockEntity
import ru.dbotthepony.mc.otm.block.entity.decorative.BreakableContainerBlockEntity
import ru.dbotthepony.mc.otm.block.entity.decorative.CargoCrateBlockEntity
import ru.dbotthepony.mc.otm.block.entity.decorative.DevChestBlockEntity
import ru.dbotthepony.mc.otm.block.entity.decorative.FluidTankBlockEntity
@ -118,6 +119,12 @@ object MBlockEntities {
val HOLO_SIGN: BlockEntityType<HoloSignBlockEntity> by registry.register(MNames.HOLO_SIGN) { BlockEntityType.Builder.of(::HoloSignBlockEntity, MBlocks.HOLO_SIGN).build(null) }
val BREAKABLE: BlockEntityType<BreakableContainerBlockEntity> by registry.register("breakable") {
val blocks = ArrayList<Block>()
blocks.add(MBlocks.SMALL_CAPSULE)
BlockEntityType.Builder.of(::BreakableContainerBlockEntity, *blocks.toTypedArray()).build(null)
}
internal fun register(bus: IEventBus) {
registry.register(bus)
bus.addListener(this::registerClient)

View File

@ -37,6 +37,7 @@ import ru.dbotthepony.mc.otm.block.MultiblockTestBlock
import ru.dbotthepony.mc.otm.block.RotatableMatteryBlock
import ru.dbotthepony.mc.otm.block.StorageCableBlock
import ru.dbotthepony.mc.otm.block.addSimpleDescription
import ru.dbotthepony.mc.otm.block.decorative.BreakableContainerBlock
import ru.dbotthepony.mc.otm.block.decorative.DevChestBlock
import ru.dbotthepony.mc.otm.block.decorative.EngineBlock
import ru.dbotthepony.mc.otm.block.decorative.FluidTankBlock
@ -434,6 +435,15 @@ object MBlocks {
val MULTIBLOCK_TEST by registry.register("multiblock_test") { MultiblockTestBlock() }
val SMALL_CAPSULE by registry.register(MNames.SMALL_CAPSULE) {
BreakableContainerBlock(
BlockBehaviour.Properties.of()
.mapColor(MapColor.COLOR_GRAY)
.destroyTime(0f)
.explosionResistance(1.5f)
)
}
init {
MRegistry.registerBlocks(registry)
}

View File

@ -680,6 +680,8 @@ object MItems {
val CONFIGURATOR: Item by registry.register(MNames.CONFIGURATOR) { ConfiguratorItem() }
val SMALL_CAPSULE by registry.register(MNames.SMALL_CAPSULE) { BlockItem(MBlocks.SMALL_CAPSULE, DEFAULT_PROPERTIES) }
init {
MRegistry.registerItems(registry)
}