Synchronized maps, update save keys, exosuit inventory upgrades as items

This commit is contained in:
DBotThePony 2022-09-05 23:13:48 +07:00
parent 2915ef675b
commit e7653d559f
Signed by: DBot
GPG Key ID: DCC23B5715498507
7 changed files with 555 additions and 48 deletions

View File

@ -56,10 +56,17 @@ private fun misc(provider: MatteryLanguageProvider) {
gui("exosuit.already_activated", "You already have exosuit following you")
gui("exosuit_upgrades.no_exosuit", "This piece of technology seems to be of no use to you.... Or is it?!")
gui("exosuit_upgrades.already_activated", "Upgrade is already active!")
gui("exosuit_upgrades.slots_upgrade", "Using this will permanently grant %s slots in ExoSuit inventory.")
gui("power_supplier.active_nodes", "Currently demanding nodes: %s")
misc("battery.single_use", "Single use battery, can not be recharged.")
misc("exosuit_upgrades.slots_upgraded", "Your exosuit has permanently gained %s slots")
misc("exosuit.granted1", "As you keep pressing fingerprint reader, you are getting hurt in finger.")
misc("exosuit.granted2", "After you raise your finger, fingerprint reader glows very bright.")
misc("exosuit.granted3", "Then, fingerprint reader fades, leaving faint trace not of your finger, but of your very soul.")

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.capability
import it.unimi.dsi.fastutil.objects.Object2IntAVLTreeMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import net.minecraft.ChatFormatting
import net.minecraft.core.Direction
@ -64,6 +65,26 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
var hasExoSuit by synchronizer.bool()
private val exoSuitSlotCountModifiersMap: MutableMap<UUID, Int> by synchronizer.Map(
keyCodec = UUIDValueCodec,
valueCodec = IntValueCodec,
backingMap = Object2IntAVLTreeMap(),
callback = {
this.exoSuitSlotCountModifiers.recompute()
}
)
val exoSuitSlotCountModifiers = UUIDIntModifiersMap(observer = observer@{
if (ply !is ServerPlayer)
return@observer
if (it < 0) {
exoSuitSlotCount = 0
} else {
exoSuitSlotCount = it
}
}, backingMap = this.exoSuitSlotCountModifiersMap)
var exoSuitSlotCount by synchronizer.int(setter = setter@{ value, access, _ ->
require(value >= 0) { "Invalid slot count $value" }
@ -334,37 +355,11 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
override fun serializeNBT(): CompoundTag {
val tag = CompoundTag()
tag["androidEnergy"] = androidEnergy.serializeNBT()
val featureList = ListTag()
for (feature in features.values) {
val featureNbt = feature.serializeNBT()
featureNbt["id"] = feature.type.registryName!!.toString()
featureList.add(featureNbt)
}
tag["features"] = featureList
tag["is_android"] = isAndroid
tag["will_become_android"] = willBecomeAndroid
val list = ListTag()
for ((type, instance) in research) {
val researchTag = instance.serializeNBT()
researchTag["id"] = type.registryName!!.toString()
list.add(researchTag)
}
tag["research"] = list
// iteration
tag["iteration"] = iteration
tag["should_send_iteration"] = shouldSendIteration
tag["shouldSendIteration"] = shouldSendIteration
tag["death_log"] = ListTag().also {
tag["deathLog"] = ListTag().also {
for ((ticks, component) in deathLog) {
it.add(CompoundTag().also {
it["ticks"] = ticks
@ -373,21 +368,47 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
}
tag["exo_suit_slot_count"] = exoSuitSlotCount
tag["has_exo_suit"] = hasExoSuit
tag["exo_suit_inventory"] = exoSuitContainer.serializeNBT()
tag["exo_suit_crafting_upgraded"] = isExoSuitCraftingUpgraded
// exosuit
tag["hasExoSuit"] = hasExoSuit
tag["exoSuitContainer"] = exoSuitContainer.serializeNBT()
tag["isExoSuitCraftingUpgraded"] = isExoSuitCraftingUpgraded
tag["exoSuitSlotCountModifiers"] = exoSuitSlotCountModifiers.serializeNBT()
// android
tag["androidEnergy"] = androidEnergy.serializeNBT()
tag["isAndroid"] = isAndroid
tag["willBecomeAndroid"] = willBecomeAndroid
val featureList = ListTag()
val researchList = ListTag()
for (feature in features.values) {
featureList.add(feature.serializeNBT().also {
it["id"] = feature.type.registryName!!.toString()
})
}
for ((type, instance) in research) {
researchList.add(instance.serializeNBT().also {
it["id"] = type.registryName!!.toString()
})
}
tag["features"] = featureList
tag["research"] = researchList
return tag
}
override fun deserializeNBT(tag: CompoundTag) {
// iterations
iteration = tag.getInt("iteration")
shouldSendIteration = tag.getBoolean("should_send_iteration")
shouldSendIteration = tag.getBoolean("shouldSendIteration")
deathLog.clear()
for (value in tag.getCompoundList("death_log")) {
for (value in tag.getCompoundList("deathLog")) {
val component = Component.Serializer.fromJson(value.getString("component"))
if (component != null) {
@ -395,9 +416,21 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
}
// exosuit
hasExoSuit = tag.getBoolean("hasExoSuit")
exoSuitContainer.deserializeNBT(tag["exoSuitContainer"])
isExoSuitCraftingUpgraded = tag.getBoolean("isExoSuitCraftingUpgraded")
tag.map("exoSuitSlotCountModifiers", exoSuitSlotCountModifiers::deserializeNBT)
// android
isAndroid = tag.getBoolean("isAndroid")
willBecomeAndroid = tag.getBoolean("willBecomeAndroid")
tag.map("androidEnergy", androidEnergy::deserializeNBT)
features.clear()
research.clear()
for (featureTag in tag.getCompoundList("features")) {
val feature = MRegistry.ANDROID_FEATURES.getValue(ResourceLocation(featureTag.getString("id")))
@ -414,10 +447,6 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
}
isAndroid = tag.getBoolean("is_android")
willBecomeAndroid = tag.getBoolean("will_become_android")
research.clear()
for (researchTag in tag.getCompoundList("research")) {
val research = MRegistry.ANDROID_RESEARCH.getValue(ResourceLocation(researchTag.getString("id")))
@ -427,11 +456,6 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
this.research[research] = instance
}
}
hasExoSuit = tag.getBoolean("has_exo_suit")
exoSuitSlotCount = tag.getInt("exo_suit_slot_count")
exoSuitContainer.deserializeNBT(tag["exo_suit_inventory"])
isExoSuitCraftingUpgraded = tag.getBoolean("exo_suit_crafting_upgraded")
}
fun dropBattery() {

View File

@ -0,0 +1,96 @@
package ru.dbotthepony.mc.otm.capability
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.Tag
import net.minecraftforge.common.util.INBTSerializable
import ru.dbotthepony.mc.otm.core.contains
import java.util.UUID
class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backingMap: MutableMap<UUID, Int> = HashMap()) : INBTSerializable<ListTag> {
var value: Int = 0
private set
fun recompute() {
var value = 0
for (mapValue in backingMap.values) {
value += mapValue
}
if (this.value != value) {
this.value = value
observer.invoke(value)
}
}
operator fun set(key: UUID, value: Int): Boolean {
val old = backingMap.put(key, value)
if (old == value) {
return false
}
this.value += value - (old ?: 0)
observer.invoke(this.value)
return true
}
operator fun get(key: UUID): Int? {
return backingMap[key]
}
operator fun contains(key: UUID): Boolean {
return backingMap.containsKey(key)
}
fun remove(key: UUID): Boolean {
if (!backingMap.containsKey(key)) {
return false
}
val old = backingMap.remove(key) ?: return true
value -= old
return true
}
fun clear() {
backingMap.clear()
val old = this.value
this.value = 0
if (old != this.value) {
observer.invoke(this.value)
}
}
override fun serializeNBT(): ListTag {
return ListTag().also {
for ((key, value) in backingMap) {
it.add(CompoundTag().also {
it.putUUID("key", key)
it.putInt("value", value)
})
}
}
}
override fun deserializeNBT(nbt: ListTag) {
backingMap.clear()
val old = this.value
this.value = 0
for (value in nbt) {
value as CompoundTag
if (value.contains("key", "value")) {
val int = value.getInt("value")
backingMap.put(value.getUUID("key"), int)
this.value += int
}
}
if (old != this.value) {
observer.invoke(this.value)
}
}
}

View File

@ -0,0 +1,96 @@
package ru.dbotthepony.mc.otm.item
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResultHolder
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Rarity
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.item.UseAnim
import net.minecraft.world.level.Level
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.runIfClient
import java.util.UUID
class ExoSuitSlotUpgradeItem(
val id: UUID?,
val slotCount: Int,
properties: Properties = Properties().stacksTo(64).rarity(Rarity.UNCOMMON).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)
) : Item(properties) {
val isCreative: Boolean get() = id == null
override fun getUseDuration(p_41454_: ItemStack): Int {
return 30
}
override fun appendHoverText(p_41421_: ItemStack, p_41422_: Level?, tooltip: MutableList<Component>, p_41424_: TooltipFlag) {
super.appendHoverText(p_41421_, p_41422_, tooltip, p_41424_)
val alreadyHasExosuit = runIfClient(true) {
minecraft.player?.matteryPlayer?.hasExoSuit == true
}
if (!alreadyHasExosuit) {
tooltip.add(TranslatableComponent("otm.gui.exosuit_upgrades.no_exosuit").withStyle(ChatFormatting.GRAY))
if (runIfClient(false) { minecraft.player?.isCreative != true }) {
return
}
}
val alreadyHas = id != null && runIfClient(false) {
minecraft.player?.matteryPlayer?.exoSuitSlotCountModifiers?.contains(id) == true
}
if (alreadyHas) {
tooltip.add(TranslatableComponent("otm.gui.exosuit_upgrades.already_activated").withStyle(ChatFormatting.DARK_RED))
}
tooltip.add(TranslatableComponent("otm.gui.exosuit_upgrades.slots_upgrade", slotCount).withStyle(ChatFormatting.DARK_GREEN))
}
override fun use(p_41432_: Level, player: Player, hand: InteractionHand): InteractionResultHolder<ItemStack> {
val matteryPlayer = player.matteryPlayer ?: return super.use(p_41432_, player, hand)
if (matteryPlayer.hasExoSuit && (id == null || id !in matteryPlayer.exoSuitSlotCountModifiers)) {
player.startUsingItem(hand)
return InteractionResultHolder.consume(player.getItemInHand(hand))
}
return super.use(p_41432_, player, hand)
}
override fun finishUsingItem(itemStack: ItemStack, level: Level, player: LivingEntity): ItemStack {
if (player !is Player) return super.finishUsingItem(itemStack, level, player)
val matteryPlayer = player.matteryPlayer ?: return super.finishUsingItem(itemStack, level, player)
if (!matteryPlayer.hasExoSuit || (id != null && id in matteryPlayer.exoSuitSlotCountModifiers)) {
return super.finishUsingItem(itemStack, level, player)
}
if (!player.abilities.instabuild)
itemStack.shrink(1)
if (player is ServerPlayer) {
if (id != null) {
matteryPlayer.exoSuitSlotCountModifiers[id] = slotCount
} else {
matteryPlayer.exoSuitSlotCountModifiers[UUID.randomUUID()] = slotCount
}
player.displayClientMessage(TranslatableComponent("otm.exosuit_upgrades.slots_upgraded", slotCount).withStyle(ChatFormatting.WHITE), false)
}
return itemStack
}
override fun getUseAnimation(p_41452_: ItemStack): UseAnim = UseAnim.BOW
}

View File

@ -8,7 +8,10 @@ import java.io.DataOutputStream
import java.io.InputStream
import java.math.BigDecimal
import java.util.*
import kotlin.ConcurrentModificationException
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KMutableProperty0
import kotlin.reflect.KProperty
@ -95,20 +98,39 @@ class EnumValueCodec<V : Enum<V>>(private val clazz: Class<out V>) : INetworkVal
}
}
sealed interface IField<V> : ReadWriteProperty<Any, V> {
sealed interface IField<V> : ReadOnlyProperty<Any, V> {
fun observe()
fun markDirty()
var value: V
val value: V
fun write(stream: DataOutputStream)
fun read(stream: DataInputStream)
override fun getValue(thisRef: Any, property: KProperty<*>): V {
return value
}
}
sealed interface IMutableField<V> : IField<V>, ReadWriteProperty<Any, V> {
override var value: V
override fun setValue(thisRef: Any, property: KProperty<*>, value: V) {
this.value = value
}
override fun getValue(thisRef: Any, property: KProperty<*>): V {
return super.getValue(thisRef, property)
}
}
data class MapChangeset<out K, out V>(
val action: MapAction,
val key: K?,
val value: V?
)
enum class MapAction {
CLEAR, ADD, REMOVE
}
class FieldSynchronizer {
@ -235,13 +257,29 @@ class FieldSynchronizer {
return ObservedField(delegate, ItemStackValueCodec)
}
fun <K, V> map(
keyCodec: INetworkValueCodec<K>,
valueCodec: INetworkValueCodec<V>,
callback: ((changes: Collection<MapChangeset<K, V>>) -> Unit)? = null,
backingMap: MutableMap<K, V> = HashMap(),
observingBackingMap: MutableMap<K, V>? = null,
): Map<K, V> {
return Map(
keyCodec = keyCodec,
valueCodec = valueCodec,
callback = callback,
backingMap = backingMap,
observingBackingMap = observingBackingMap
)
}
inner class Field<V>(
private var field: V,
private val codec: INetworkValueCodec<V>,
private val getter: FieldGetter<V>? = null,
private val setter: FieldSetter<V>? = null,
isObserver: Boolean = false,
) : IField<V> {
) : IMutableField<V> {
private var remote: V = codec.copy(field)
val id = fields.size + 1
@ -335,7 +373,7 @@ class FieldSynchronizer {
}
}
inner class ObservedField<V> : IField<V> {
inner class ObservedField<V> : IMutableField<V> {
private val codec: NetworkValueCodec<V>
private val getter: () -> V
private val setter: (V) -> Unit
@ -397,6 +435,211 @@ class FieldSynchronizer {
}
}
companion object {
private val ClearBacklogEntry = { stream: DataOutputStream -> stream.write(MapAction.CLEAR.ordinal + 1) }
private val MapActionList = MapAction.values()
private val ClearMapChangeset = MapChangeset(MapAction.CLEAR, null, null)
}
inner class Map<K, V>(
private val keyCodec: INetworkValueCodec<K>,
private val valueCodec: INetworkValueCodec<V>,
private val backingMap: MutableMap<K, V>,
private val observingBackingMap: MutableMap<K, V>? = null,
private val callback: ((changes: Collection<MapChangeset<K, V>>) -> Unit)? = null
) : IField<MutableMap<K, V>> {
private var isDirty = false
private var sentAllValues = false
private var isRemote = false
val id = fields.size + 1
init {
fields.add(this)
if (observingBackingMap != null)
observers.add(this)
}
private val backlog = LinkedList<(DataOutputStream) -> Unit>()
override fun observe() {
if (isRemote) {
return
}
val observingBackingMap = observingBackingMap
if (observingBackingMap != null) {
for ((key, value) in backingMap) {
val remoteValue = observingBackingMap[key] ?: throw ConcurrentModificationException("Backing map of $this was modified externally, or $value missed a modification")
if (!valueCodec.compare(value, remoteValue)) {
val valueCopy = valueCodec.copy(value)
backlog.add {
it.write(MapAction.ADD.ordinal + 1)
keyCodec.write(it, key)
valueCodec.write(it, valueCopy)
}
observingBackingMap[key] = valueCopy
if (!isDirty) {
dirtyFields.add(this)
isDirty = true
}
}
}
}
}
override fun markDirty() {
if (isRemote) {
return
}
if (!isDirty) {
dirtyFields.add(this)
isDirty = true
}
if (!sentAllValues) {
for ((key, value) in backingMap) {
val valueCopy = valueCodec.copy(value)
backlog.add {
it.write(MapAction.ADD.ordinal + 1)
keyCodec.write(it, key)
valueCodec.write(it, valueCopy)
}
observingBackingMap?.put(key, valueCopy)
}
sentAllValues = true
}
}
override val value: MutableMap<K, V> = object : ObservedMap<K, V>(backingMap) {
override fun onClear() {
if (isRemote) {
return
}
backlog.clear()
observingBackingMap?.clear()
backlog.add(ClearBacklogEntry)
if (!isDirty) {
dirtyFields.add(this@Map)
isDirty = true
}
}
override fun onValueAdded(key: K, value: V) {
if (isRemote) {
return
}
val valueCopy = valueCodec.copy(value)
backlog.add {
it.write(MapAction.ADD.ordinal + 1)
keyCodec.write(it, key)
valueCodec.write(it, valueCopy)
}
observingBackingMap?.put(key, valueCopy)
if (!isDirty) {
dirtyFields.add(this@Map)
isDirty = true
}
}
override fun onValueRemoved(key: K, value: V) {
if (isRemote) {
return
}
val keyCopy = keyCodec.copy(key)
backlog.add {
it.write(MapAction.REMOVE.ordinal + 1)
keyCodec.write(it, keyCopy)
}
observingBackingMap?.remove(key)
if (!isDirty) {
dirtyFields.add(this@Map)
isDirty = true
}
}
}
override fun write(stream: DataOutputStream) {
stream.write(id)
sentAllValues = false
isDirty = false
for (entry in backlog) {
entry.invoke(stream)
}
backlog.clear()
stream.write(0)
}
override fun read(stream: DataInputStream) {
if (!isRemote) {
isRemote = true
backlog.clear()
observingBackingMap?.clear()
}
isDirty = false
val changeset = LinkedList<MapChangeset<K, V>>()
var readAction = stream.read() - 1
while (readAction != -1) {
if (readAction >= MapActionList.size) {
throw IndexOutOfBoundsException("Unknown map action with ID $readAction")
}
when (MapActionList[readAction]) {
MapAction.CLEAR -> {
backingMap.clear()
changeset.add(ClearMapChangeset)
}
MapAction.ADD -> {
val key = keyCodec.read(stream)
val value = valueCodec.read(stream)
backingMap[key] = value
changeset.add(MapChangeset(MapAction.ADD, key, value))
}
MapAction.REMOVE -> {
val key = keyCodec.read(stream)
backingMap.remove(key)
changeset.add(MapChangeset(MapAction.REMOVE, key, null))
}
}
readAction = stream.read() - 1
}
if (changeset.size != 0) {
callback?.invoke(changeset)
}
}
}
fun invalidate() {
for (field in fields) {
field.markDirty()
@ -438,6 +681,10 @@ class FieldSynchronizer {
i++
}
if (stream.read() != -1) {
throw IllegalStateException("Stream wasn't fully drain!")
}
return i
}

View File

@ -7,6 +7,7 @@ import net.minecraftforge.registries.RegistryObject
class LazyList<T>(private val getters: ImmutableList<() -> T>) : AbstractList<T>() {
constructor(vararg getters: () -> T) : this(ImmutableList.copyOf(getters))
constructor(getters: List<() -> T>) : this(ImmutableList.copyOf(getters))
override val size: Int
get() = getters.size

View File

@ -14,11 +14,14 @@ import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext
import net.minecraftforge.registries.DeferredRegister
import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.RegistryObject
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
import ru.dbotthepony.mc.otm.item.*
import ru.dbotthepony.mc.otm.item.weapon.PlasmaRifleItem
import java.util.*
import kotlin.collections.ArrayList
object MItems {
private val DEFAULT_PROPERTIES = Item.Properties().stacksTo(64).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)
@ -76,6 +79,39 @@ object MItems {
}
val EXOSUIT_PROBE: Item by registry.register(MNames.EXOSUIT_PROBE, ::ExoSuitProbeItem)
val EXOSUIT_INVENTORY_UPGRADE_CREATIVE: Item by registry.register("exosuit_inventory_upgrade_creative") { ExoSuitSlotUpgradeItem(null, 9, Item.Properties().stacksTo(64).rarity(Rarity.EPIC).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) }
val EXOSUIT_INVENTORY_UPGRADES: List<Item>
val EXOSUIT_INVENTORY_UPGRADES_CRAFTABLE: List<Item>
val EXOSUIT_INVENTORY_UPGRADES_UNCRAFTABLE: List<Item>
init {
val upgrades = ArrayList<() -> Item>()
val upgradesCraftable = ArrayList<() -> Item>()
val upgradesUncraftable = ArrayList<() -> Item>()
val baseSignificant = 7344348239534784L
var baseInsignificant = 848473865769484L
for (i in 1 .. 4) {
val obj = registry.register("exosuit_inventory_upgrade_$i") { ExoSuitSlotUpgradeItem(UUID(baseSignificant, baseInsignificant++), 9) }
upgrades.add(obj::get)
upgradesCraftable.add(obj::get)
}
for (i in 1 .. 16) {
val obj = registry.register("exosuit_inventory_upgrade_uncraftable_$i") { ExoSuitSlotUpgradeItem(UUID(baseSignificant, baseInsignificant++), 9 + (i / 5) * 9) }
upgrades.add(obj::get)
upgradesUncraftable.add(obj::get)
}
EXOSUIT_INVENTORY_UPGRADES = LazyList(upgrades)
EXOSUIT_INVENTORY_UPGRADES_CRAFTABLE = LazyList(upgradesCraftable)
EXOSUIT_INVENTORY_UPGRADES_UNCRAFTABLE = LazyList(upgradesUncraftable)
}
val EXOSUIT_INVENTORY_UPGRADE_BIG: Item by registry.register("exosuit_inventory_upgrade_big") { ExoSuitSlotUpgradeItem(UUID.fromString("121a17a5-533c-9ac0-ff02-03aea75ed20c"), 45, Item.Properties().stacksTo(64).rarity(Rarity.RARE).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) }
val EXOSUIT_INVENTORY_UPGRADE_HUGE: Item by registry.register("exosuit_inventory_upgrade_huge") { ExoSuitSlotUpgradeItem(UUID.fromString("121a17a5-533c-9ac0-ff02-03aea75ed20d"), 90, Item.Properties().stacksTo(64).rarity(Rarity.RARE).tab(OverdriveThatMatters.INSTANCE.CREATIVE_TAB)) }
val DEBUG_EXPLOSION_SMALL: Item by registry.register(MNames.DEBUG_EXPLOSION_SMALL) { BlockItem(MBlocks.DEBUG_EXPLOSION_SMALL, DEFAULT_PROPERTIES) }
val DEBUG_SPHERE_POINTS: Item by registry.register(MNames.DEBUG_SPHERE_POINTS) { BlockItem(MBlocks.DEBUG_SPHERE_POINTS, DEFAULT_PROPERTIES) }