feat: projections

This commit is contained in:
xtex 2023-06-30 12:06:56 +08:00
parent 482e4af4e7
commit 47e8b22a87
Signed by: xtex
GPG Key ID: B918086ED8045B91
14 changed files with 411 additions and 76 deletions

View File

@ -0,0 +1,19 @@
package quaedam.mixin;
import net.minecraft.core.registries.BuiltInRegistries;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import quaedam.projection.ProjectionEffectType;
@Mixin(BuiltInRegistries.class)
public class MixinBuiltInRegistries {
@Inject(at = @At("HEAD"), method = "bootStrap()V")
private static void bootStrap(CallbackInfo info) {
// init projection effect type registry
ProjectionEffectType.Companion.getRegistry();
}
}

View File

@ -1,63 +0,0 @@
package quaedam
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
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.material.MapColor
import net.minecraft.world.phys.BlockHitResult
object Projector {
const val ID = "projector"
val block = Quaedam.blocks.register(ID) { ProjectorBlock }
val item = Quaedam.items.register(ID) {
BlockItem(
ProjectorBlock, Item.Properties()
.stacksTo(1)
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
}
object ProjectorBlock : Block(Properties.of()
.jumpFactor(0.8f)
.lightLevel { 3 }
.mapColor(MapColor.COLOR_BLACK)
.randomTicks()
.strength(4.0f)
.requiresCorrectToolForDrops()) {
@Suppress("OVERRIDE_DEPRECATION")
override fun use(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
interactionHand: InteractionHand,
blockHitResult: BlockHitResult
): InteractionResult {
return InteractionResult.SUCCESS
}
@Suppress("OVERRIDE_DEPRECATION")
override fun randomTick(
blockState: BlockState,
serverLevel: ServerLevel,
blockPos: BlockPos,
randomSource: RandomSource
) {
// @TODO: call projectorRandomTick
}
}

View File

@ -6,10 +6,11 @@ import dev.architectury.registry.registries.RegistrySupplier
import net.minecraft.core.registries.Registries import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.world.item.CreativeModeTab import net.minecraft.world.item.CreativeModeTab
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items import net.minecraft.world.item.Items
import quaedam.projection.ProjectionEffectType
import quaedam.projection.SkylightProjection import quaedam.projection.SkylightProjection
import quaedam.projector.Projector
object Quaedam { object Quaedam {
@ -18,6 +19,8 @@ object Quaedam {
val creativeModeTabs = DeferredRegister.create(ID, Registries.CREATIVE_MODE_TAB)!! val creativeModeTabs = DeferredRegister.create(ID, Registries.CREATIVE_MODE_TAB)!!
val items = DeferredRegister.create(ID, Registries.ITEM)!! val items = DeferredRegister.create(ID, Registries.ITEM)!!
val blocks = DeferredRegister.create(ID, Registries.BLOCK)!! val blocks = DeferredRegister.create(ID, Registries.BLOCK)!!
val blockEntities = DeferredRegister.create(ID, Registries.BLOCK_ENTITY_TYPE)!!
val projectionEffects = DeferredRegister.create(ID, ProjectionEffectType.registryKey)!!
val creativeModeTab: RegistrySupplier<CreativeModeTab> = creativeModeTabs.register("quaedam") { val creativeModeTab: RegistrySupplier<CreativeModeTab> = creativeModeTabs.register("quaedam") {
CreativeTabRegistry.create(Component.translatable("category.quaedam")) { CreativeTabRegistry.create(Component.translatable("category.quaedam")) {
@ -28,9 +31,12 @@ object Quaedam {
fun init() { fun init() {
Projector Projector
SkylightProjection SkylightProjection
creativeModeTabs.register() creativeModeTabs.register()
items.register() items.register()
blocks.register() blocks.register()
blockEntities.register()
projectionEffects.register()
} }
} }

View File

@ -1,15 +1,19 @@
package quaedam.projection package quaedam.projection
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel import net.minecraft.world.entity.LivingEntity
import net.minecraft.util.RandomSource
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.material.MapColor import net.minecraft.world.level.material.MapColor
import net.minecraft.world.level.storage.loot.LootParams import net.minecraft.world.level.storage.loot.LootParams
import quaedam.projector.ProjectorBlockEntity
import quaedam.utils.getChunksNearby
abstract class ProjectionBlock(properties: Properties = createProperties()) : Block(properties) { abstract class ProjectionBlock<P : ProjectionEffect>(properties: Properties = createProperties()) : Block(properties),
ProjectionProvider<P> {
companion object { companion object {
fun createProperties(): Properties = Properties.of() fun createProperties(): Properties = Properties.of()
@ -17,18 +21,39 @@ abstract class ProjectionBlock(properties: Properties = createProperties()) : Bl
.requiresCorrectToolForDrops() .requiresCorrectToolForDrops()
.mapColor(MapColor.COLOR_GRAY) .mapColor(MapColor.COLOR_GRAY)
fun findNearbyProjectors(level: Level, pos: BlockPos) = level.getChunksNearby(pos, 1)
.flatMap {
it.blockEntities.filter { (k, v) -> v is ProjectorBlockEntity }
.keys
.filterNotNull()
}
.toSet()
} }
@Suppress("OVERRIDE_DEPRECATION") @Suppress("OVERRIDE_DEPRECATION")
override fun getDrops(blockState: BlockState, builder: LootParams.Builder) = listOf(ItemStack(asItem())) override fun getDrops(blockState: BlockState, builder: LootParams.Builder) = listOf(ItemStack(asItem()))
fun projectionActivated(level: ServerLevel, projectorPos: BlockPos, projectionPos: BlockPos) { override fun setPlacedBy(
level: Level,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
itemStack: ItemStack
) {
super.setPlacedBy(level, pos, state, placer, itemStack)
if (!level.isClientSide) {
findNearbyProjectors(level, pos)
.forEach { (level.getBlockEntity(it) as ProjectorBlockEntity).checkUpdate() }
}
} }
fun projectionDeactivated(level: ServerLevel, projectorPos: BlockPos, projectionPos: BlockPos) { override fun destroy(level: LevelAccessor, pos: BlockPos, state: BlockState) {
} super.destroy(level, pos, state)
if (level is Level && !level.isClientSide) {
fun projectorRandomTick(level: ServerLevel, projectorPos: BlockPos, projectionPos: BlockPos, random: RandomSource) { findNearbyProjectors(level, pos)
.forEach { (level.getBlockEntity(it) as ProjectorBlockEntity).checkUpdate() }
}
} }
} }

View File

@ -0,0 +1,54 @@
package quaedam.projection
import net.minecraft.core.BlockPos
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.level.block.state.BlockState
abstract class ProjectionEffect {
abstract val type: ProjectionEffectType<*>
abstract fun toNbt(tag: CompoundTag)
abstract fun fromNbt(tag: CompoundTag)
fun toNbt() = CompoundTag().apply { toNbt(this) }
override fun equals(other: Any?) = other === this
override fun hashCode() = type.hashCode()
fun activated(level: ServerLevel, projectorPos: BlockPos) {
}
fun deactivated(level: ServerLevel, projectorPos: BlockPos) {
}
fun randomTick(level: ServerLevel, projectorPos: BlockPos, random: RandomSource) {
}
}
data class ProjectionEffectType<T : ProjectionEffect>(val constructor: () -> T) {
companion object {
val registryKey: ResourceKey<Registry<ProjectionEffectType<*>>> =
ResourceKey.createRegistryKey(ResourceLocation("quaedam", "projection_effect"))
val registry: Registry<ProjectionEffectType<*>> = BuiltInRegistries.registerSimple(registryKey) { null }
}
val id by lazy { registry.getResourceKey(this).get().location()!! }
}
interface ProjectionProvider<P : ProjectionEffect> {
fun createProjectionEffect(level: ServerLevel, state: BlockState, pos: BlockPos): P?
}

View File

@ -1,14 +1,18 @@
package quaedam.projection package quaedam.projection
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.item.BlockItem import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.level.block.state.BlockState
import quaedam.Quaedam import quaedam.Quaedam
object SkylightProjection { object SkylightProjection {
const val ID = "skylight_projection" const val ID = "skylight_projection"
val block = Quaedam.blocks.register(ID) { SkylightProjectionBlock } val block = Quaedam.blocks.register(ID) { SkylightProjectionBlock }!!
val item = Quaedam.items.register(ID) { val item = Quaedam.items.register(ID) {
BlockItem( BlockItem(
@ -17,6 +21,31 @@ object SkylightProjection {
) )
}!! }!!
val effect = Quaedam.projectionEffects.register(ID) {
ProjectionEffectType { SkylightProjectionEffect }
}!!
} }
object SkylightProjectionBlock : ProjectionBlock(createProperties().lightLevel { 3 }) object SkylightProjectionBlock : ProjectionBlock<SkylightProjectionEffect>(createProperties().lightLevel { 3 }) {
override fun createProjectionEffect(
level: ServerLevel,
state: BlockState,
pos: BlockPos
) = SkylightProjectionEffect
}
object SkylightProjectionEffect : ProjectionEffect() {
override val type
get() = SkylightProjection.effect.get()!!
override fun toNbt(tag: CompoundTag) {
}
override fun fromNbt(tag: CompoundTag) {
}
}

View File

@ -0,0 +1,26 @@
package quaedam.projector
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.Item
import net.minecraft.world.level.block.entity.BlockEntityType
import quaedam.Quaedam
object Projector {
const val ID = "projector"
val block = Quaedam.blocks.register(ID) { ProjectorBlock }!!
val item = Quaedam.items.register(ID) {
BlockItem(
ProjectorBlock, Item.Properties()
.stacksTo(1)
.`arch$tab`(Quaedam.creativeModeTab)
)
}!!
val blockEntity = Quaedam.blockEntities.register(ID) {
BlockEntityType.Builder.of(::ProjectorBlockEntity, block.get()).build(null)
}!!
}

View File

@ -0,0 +1,84 @@
package quaedam.projector
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.material.MapColor
import net.minecraft.world.phys.BlockHitResult
import quaedam.projection.ProjectionBlock
object ProjectorBlock : Block(Properties.of()
.jumpFactor(0.8f)
.lightLevel { 3 }
.mapColor(MapColor.COLOR_BLACK)
.randomTicks()
.strength(4.0f)
.requiresCorrectToolForDrops()), EntityBlock {
fun checkUpdate(level: Level, pos: BlockPos) {
if (!level.isClientSide) {
(level.getBlockEntity(pos) as ProjectorBlockEntity).checkUpdate()
}
}
@Suppress("OVERRIDE_DEPRECATION")
override fun use(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
player: Player,
interactionHand: InteractionHand,
blockHitResult: BlockHitResult
): InteractionResult {
checkUpdate(level, blockPos)
return InteractionResult.SUCCESS
}
override fun newBlockEntity(pos: BlockPos, state: BlockState) = ProjectorBlockEntity(pos, state)
@Suppress("OVERRIDE_DEPRECATION")
override fun randomTick(
state: BlockState,
level: ServerLevel,
pos: BlockPos,
random: RandomSource
) {
// @TODO: call projectorRandomTick
checkUpdate(level, pos)
}
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
override fun neighborChanged(
state: BlockState,
level: Level,
pos: BlockPos,
sourceBlock: Block,
sourcePos: BlockPos,
notify: Boolean
) {
super.neighborChanged(state, level, pos, sourceBlock, sourcePos, notify)
checkUpdate(level, pos)
}
override fun setPlacedBy(
level: Level,
pos: BlockPos,
state: BlockState,
placer: LivingEntity?,
itemStack: ItemStack
) {
super.setPlacedBy(level, pos, state, placer, itemStack)
checkUpdate(level, pos)
}
}

View File

@ -0,0 +1,126 @@
package quaedam.projector
import net.minecraft.core.BlockPos
import net.minecraft.core.Vec3i
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.protocol.Packet
import net.minecraft.network.protocol.game.ClientGamePacketListener
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.levelgen.structure.BoundingBox
import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType
import quaedam.projection.ProjectionProvider
import quaedam.utils.sendBlockUpdated
class ProjectorBlockEntity(pos: BlockPos, state: BlockState) :
BlockEntity(Projector.blockEntity.get(), pos, state) {
companion object {
const val EFFECT_RADIUS = 4
}
val effectAreaChunk by lazy {
val chunk = level!!.getChunk(pos).pos
ChunkPos(chunk.x - EFFECT_RADIUS, chunk.z - EFFECT_RADIUS) to
ChunkPos(chunk.x + EFFECT_RADIUS, chunk.z + EFFECT_RADIUS)
}
val effectArea: BoundingBox by lazy {
val chunk = level!!.getChunk(pos).pos
val (minChunk, maxChunk) = effectAreaChunk
val minBlock = BlockPos(minChunk.minBlockX, level!!.minBuildHeight, minChunk.minBlockZ)
val maxBlock = BlockPos(maxChunk.maxBlockX, level!!.maxBuildHeight, maxChunk.maxBlockZ)
BoundingBox.fromCorners(minBlock, maxBlock)
}
val checkArea: BoundingBox by lazy {
BoundingBox.fromCorners(pos.offset(-2, -1, -2), pos.offset(2, -2, 2))
}
var effects: Map<ProjectionEffectType<*>, ProjectionEffect> = emptyMap()
override fun saveAdditional(tag: CompoundTag) {
super.saveAdditional(tag)
val effectsTag = CompoundTag()
effects.map { (type, effect) ->
effectsTag.put(type.id.toString(), effect.toNbt())
}
tag.put("ProjectionEffects", effectsTag)
}
override fun load(tag: CompoundTag) {
super.load(tag)
val effectsTag = tag["ProjectionEffects"]
val effects = mutableMapOf<ProjectionEffectType<*>, ProjectionEffect>()
if (effectsTag != null && effectsTag is CompoundTag) {
effectsTag.allKeys.forEach { id ->
val type = ProjectionEffectType.registry[ResourceLocation(id)]
if (type != null) {
val effect = type.constructor().apply { fromNbt(effectsTag[id] as CompoundTag) }
effects[type] = effect
}
}
}
updateEffects(effects)
}
override fun getUpdateTag(): CompoundTag = saveWithoutMetadata()
override fun getUpdatePacket(): Packet<ClientGamePacketListener> = ClientboundBlockEntityDataPacket.create(this)
override fun setRemoved() {
super.setRemoved()
updateEffects(emptyMap(), notify = false)
}
operator fun contains(pos: Vec3i) = effectArea.isInside(pos)
operator fun contains(pos: ChunkPos) =
this.contains(Vec3i(pos.middleBlockX, level!!.minBuildHeight, pos.middleBlockZ))
fun checkUpdate() {
if (level!!.isClientSide)
return
val effects = collectEffects()
updateEffects(effects)
}
fun updateEffects(effects: Map<ProjectionEffectType<*>, ProjectionEffect>, notify: Boolean = true) {
if (effects != this.effects) {
this.effects = effects
if (!level!!.isClientSide) {
sendBlockUpdated()
}
}
}
fun collectEffects(): Map<ProjectionEffectType<*>, ProjectionEffect> {
val level = level!! as ServerLevel
if (!level.getBlockState(blockPos.below()).isAir) {
return emptyMap()
}
val effects = mutableMapOf<ProjectionEffectType<*>, ProjectionEffect>()
for (x in checkArea.minX()..checkArea.maxX()) {
for (y in checkArea.minY()..checkArea.maxY()) {
for (z in checkArea.minZ()..checkArea.maxZ()) {
val pos = BlockPos(x, y, z)
val blockState = level.getBlockState(pos)
val block = blockState.block
if (block is ProjectionProvider<*>) {
val projection = block.createProjectionEffect(level, blockState, pos)
if (projection != null) {
effects[projection.type] = projection
}
}
}
}
}
return effects
}
}

View File

@ -0,0 +1,7 @@
package quaedam.utils
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
fun BlockEntity.sendBlockUpdated() =
level!!.sendBlockUpdated(blockPos, blockState, blockState, Block.UPDATE_CLIENTS)

View File

@ -0,0 +1,16 @@
package quaedam.utils
import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.chunk.LevelChunk
fun Level.getChunksNearby(pos: BlockPos, radius: Int): Set<LevelChunk> {
val chunkX = SectionPos.blockToSectionCoord(pos.x)
val chunkZ = SectionPos.blockToSectionCoord(pos.z)
return (chunkX - radius..chunkX + radius).flatMap { x ->
(chunkZ - radius..chunkZ + radius).map { z ->
getChunk(x, z)
}
}.toSet()
}

View File

@ -0,0 +1,3 @@
{
"parent": "quaedam:block/skylight_projection"
}

View File

@ -5,6 +5,7 @@
"client": [ "client": [
], ],
"mixins": [ "mixins": [
"MixinBuiltInRegistries"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1

View File

@ -1 +1,3 @@
accessWidener v2 named accessWidener v2 named
# Custom Registry for ProjectionEffect
accessible method net/minecraft/core/registries/BuiltInRegistries registerSimple (Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/core/registries/BuiltInRegistries$RegistryBootstrap;)Lnet/minecraft/core/Registry;