feat: bare projection shell

This commit is contained in:
xtex 2023-07-21 17:56:28 +08:00
parent 899eac954e
commit d88f110fe5
Signed by: xtex
GPG Key ID: B918086ED8045B91
18 changed files with 489 additions and 12 deletions

View File

@ -0,0 +1,24 @@
package quaedam.mixin;
import net.minecraft.core.GlobalPos;
import net.minecraft.server.MinecraftServer;
import org.spongepowered.asm.mixin.Mixin;
import quaedam.mixininterface.ProjectionShellMutexAccessor;
import quaedam.shell.ProjectionShellMutex;
import java.util.LinkedHashMap;
@Mixin(MinecraftServer.class)
public class MixinMinecraftServer implements ProjectionShellMutexAccessor {
private LinkedHashMap<GlobalPos, ProjectionShellMutex.Lock> quaedam$projectionShellMutex;
@Override
public LinkedHashMap<GlobalPos, ProjectionShellMutex.Lock> quaedam$getProjectionShellMutex() {
if (quaedam$projectionShellMutex == null) {
quaedam$projectionShellMutex = new LinkedHashMap<>();
}
return quaedam$projectionShellMutex;
}
}

View File

@ -0,0 +1,12 @@
package quaedam.mixininterface;
import net.minecraft.core.GlobalPos;
import quaedam.shell.ProjectionShellMutex;
import java.util.LinkedHashMap;
public interface ProjectionShellMutexAccessor {
LinkedHashMap<GlobalPos, ProjectionShellMutex.Lock> quaedam$getProjectionShellMutex();
}

View File

@ -17,6 +17,7 @@ import quaedam.projection.misc.SkylightProjection
import quaedam.projection.misc.SoundProjection import quaedam.projection.misc.SoundProjection
import quaedam.projection.swarm.SwarmProjection import quaedam.projection.swarm.SwarmProjection
import quaedam.projector.Projector import quaedam.projector.Projector
import quaedam.shell.ProjectionShell
object Quaedam { object Quaedam {
@ -50,6 +51,7 @@ object Quaedam {
NoiseProjection NoiseProjection
ProjectionCommand ProjectionCommand
SimpleProjectionUpdate SimpleProjectionUpdate
ProjectionShell
creativeModeTabs.register() creativeModeTabs.register()
items.register() items.register()

View File

@ -8,10 +8,12 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.block.EntityBlock import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.ProjectionShellBlock
import quaedam.utils.sendBlockUpdated import quaedam.utils.sendBlockUpdated
abstract class EntityProjectionBlock<P : ProjectionEffect>(properties: Properties = createProperties()) : abstract class EntityProjectionBlock<P : ProjectionEffect>(properties: Properties = createProperties()) :
ProjectionBlock<P>(properties), EntityBlock { ProjectionBlock<P>(properties), EntityBlock, ProjectionShellBlock {
companion object { companion object {
fun createProperties(): Properties = ProjectionBlock.createProperties() fun createProperties(): Properties = ProjectionBlock.createProperties()
@ -40,4 +42,11 @@ abstract class EntityProjectionBlock<P : ProjectionEffect>(properties: Propertie
} }
} }
override fun getProjectionEffectForShell(level: Level, pos: BlockPos) =
(getBlockEntity(level, pos).cloneProjection() as ProjectionEffectShell.Provider).createShell()
override fun applyFromShell(level: Level, pos: BlockPos, shell: ProjectionEffectShell) = applyChange(level, pos) {
fromNbt(shell.effect.toNbt())
}
} }

View File

@ -1,20 +1,15 @@
package quaedam.projection.misc package quaedam.projection.misc
import net.minecraft.core.BlockPos
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
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.BlockItem
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.BlockHitResult
import quaedam.Quaedam import quaedam.Quaedam
import quaedam.projection.EntityProjectionBlock import quaedam.projection.EntityProjectionBlock
import quaedam.projection.ProjectionEffect import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType import quaedam.projection.ProjectionEffectType
import quaedam.projection.SimpleProjectionEntity import quaedam.projection.SimpleProjectionEntity
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.buildProjectionEffectShell
import kotlin.math.min import kotlin.math.min
object SkylightProjection { object SkylightProjection {
@ -47,7 +42,7 @@ object SkylightProjectionBlock : EntityProjectionBlock<SkylightProjectionEffect>
} }
data class SkylightProjectionEffect(var factor: Double = 2.0) : ProjectionEffect() { data class SkylightProjectionEffect(var factor: Double = 2.0) : ProjectionEffect(), ProjectionEffectShell.Provider {
companion object { companion object {
const val TAG_FACTOR = "Factor" const val TAG_FACTOR = "Factor"
@ -67,4 +62,8 @@ data class SkylightProjectionEffect(var factor: Double = 2.0) : ProjectionEffect
} }
} }
override fun createShell() = buildProjectionEffectShell(this) {
doubleSlider("quaedam.shell.skylight.factor", ::factor, 0.0..5.0, 0.1)
}
} }

View File

@ -0,0 +1,66 @@
package quaedam.shell
import net.minecraft.client.gui.components.AbstractSliderButton
import net.minecraft.client.gui.components.CycleButton
import net.minecraft.client.gui.components.StringWidget
import net.minecraft.client.gui.layouts.LayoutElement
import net.minecraft.network.chat.Component
import quaedam.projection.ProjectionEffect
import kotlin.math.floor
import kotlin.reflect.KMutableProperty0
class ProjectionEffectShell(val effect: ProjectionEffect) {
interface Provider {
fun createShell(): ProjectionEffectShell
}
val rows = mutableListOf<Row>()
val width = 150
val height = 20
data class Row(val text: Component, val renderer: ShellRenderer)
fun row(key: String, renderer: ShellRenderer) {
rows += Row(Component.translatable(key), renderer)
}
fun text(key: String, value: Component) = row(key) { StringWidget(value, it.font) }
fun doubleSlider(key: String, property: KMutableProperty0<Double>, range: ClosedRange<Double>, step: Double) =
row(key) {
object : AbstractSliderButton(
0, 0, width, height,
Component.literal(property.get().toString()), property.get()
) {
override fun updateMessage() {
message = Component.literal(value.toString())
}
override fun applyValue() {
value = floor(value / step) * step
property.set(value)
}
}
}
fun intCycle(key: String, property: KMutableProperty0<Int>, range: IntProgression) =
row(key) {
CycleButton.builder<Int> { Component.literal(it.toString()) }
.displayOnlyValue()
.withValues(range.toList())
.create(0, 0, width, height, Component.translatable(key))
}
}
inline fun buildProjectionEffectShell(effect: ProjectionEffect, builder: ProjectionEffectShell.() -> Unit) =
ProjectionEffectShell(effect).apply(builder)
data class ShellRenderContext(val screen: ProjectionShellScreen) {
val font get() = screen.getFont()
}
typealias ShellRenderer = (ctx: ShellRenderContext) -> LayoutElement

View File

@ -0,0 +1,26 @@
package quaedam.shell
import dev.architectury.networking.NetworkChannel
import net.minecraft.resources.ResourceLocation
import quaedam.Quaedam
import quaedam.shell.network.ClientboundPSHLockResultPacket
import quaedam.shell.network.ClientboundPSHLockRevokePacket
import quaedam.shell.network.ServerboundPSHLockAcquirePacket
import quaedam.shell.network.ServerboundPSHLockReleasePacket
object ProjectionShell {
const val ID = "projection_shell"
val item = Quaedam.items.register(ID) { ProjectionShellItem }!!
val channel = NetworkChannel.create(ResourceLocation("quaedam", ID))
init {
ServerboundPSHLockAcquirePacket
ServerboundPSHLockReleasePacket
ClientboundPSHLockRevokePacket
ClientboundPSHLockResultPacket
}
}

View File

@ -0,0 +1,12 @@
package quaedam.shell
import net.minecraft.core.BlockPos
import net.minecraft.world.level.Level
interface ProjectionShellBlock {
fun getProjectionEffectForShell(level: Level, pos: BlockPos): ProjectionEffectShell
fun applyFromShell(level: Level, pos: BlockPos, shell: ProjectionEffectShell)
}

View File

@ -0,0 +1,24 @@
package quaedam.shell
import net.minecraft.world.InteractionResult
import net.minecraft.world.item.Item
import net.minecraft.world.item.context.UseOnContext
import quaedam.Quaedam
import quaedam.shell.network.ServerboundPSHLockAcquirePacket
object ProjectionShellItem : Item(
Properties()
.stacksTo(1)
.`arch$tab`(Quaedam.creativeModeTab)
) {
override fun useOn(context: UseOnContext): InteractionResult {
val block = context.level.getBlockState(context.clickedPos).block
if (block is ProjectionShellBlock && context.level.isClientSide) {
ProjectionShell.channel.sendToServer(ServerboundPSHLockAcquirePacket(context.clickedPos))
return InteractionResult.SUCCESS
}
return InteractionResult.PASS
}
}

View File

@ -0,0 +1,49 @@
package quaedam.shell
import dev.architectury.event.events.common.TickEvent
import net.minecraft.core.BlockPos
import net.minecraft.core.GlobalPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import quaedam.mixininterface.ProjectionShellMutexAccessor
import quaedam.shell.network.ClientboundPSHLockRevokePacket
object ProjectionShellMutex {
init {
TickEvent.SERVER_POST.register { server ->
val mutex = (server as ProjectionShellMutexAccessor).`quaedam$getProjectionShellMutex`()
val currentTime = System.currentTimeMillis()
mutex.forEach { pos, lock ->
if (currentTime - lock.time > 60 * 1000) {
mutex.remove(pos)
ProjectionShell.channel.sendToPlayer(lock.player, ClientboundPSHLockRevokePacket)
}
}
}
}
fun tryLock(level: ServerLevel, pos: BlockPos, player: ServerPlayer): Boolean {
val mutex = (level.server as ProjectionShellMutexAccessor).`quaedam$getProjectionShellMutex`()
val gPos = GlobalPos.of(level.dimension(), pos)
if (mutex.values.any { it.player == player }) {
return false
}
if (gPos !in mutex) {
mutex[gPos] = Lock(player, System.currentTimeMillis())
return true
}
return false
}
fun release(level: ServerLevel, pos: BlockPos, player: ServerPlayer) {
val mutex = (level.server as ProjectionShellMutexAccessor).`quaedam$getProjectionShellMutex`()
val gPos = GlobalPos.of(level.dimension(), pos)
if (mutex[gPos]?.player == player) {
mutex.remove(gPos)
}
}
data class Lock(val player: ServerPlayer, val time: Long)
}

View File

@ -0,0 +1,53 @@
package quaedam.shell
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.StringWidget
import net.minecraft.client.gui.layouts.GridLayout
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.world.level.Level
import quaedam.shell.network.ServerboundPSHLockReleasePacket
class ProjectionShellScreen(val level: Level, val pos: BlockPos, val shell: ProjectionEffectShell) :
Screen(Component.translatable("quaedam.screen.projection_shell")) {
val layout = GridLayout()
override fun init() {
super.init()
layout.spacing(4)
val rows = layout.createRowHelper(2)
val renderContext = ShellRenderContext(this)
shell.rows.forEach {
rows.addChild(StringWidget(150, 20, it.text, font))
rows.addChild(it.renderer(renderContext))
}
run { // Buttons
rows.addChild(StringWidget(Component.empty(), font))
rows.addChild(Button.builder(Component.translatable("quaedam.screen.projection_shell.save")) {
val block = level.getBlockState(pos).block
if (block is ProjectionShellBlock) {
block.applyFromShell(level, pos, shell)
}
}.build())
}
layout.arrangeElements()
layout.visitWidgets(::addRenderableWidget)
}
fun getFont() = font
override fun renderBackground(guiGraphics: GuiGraphics) {
super.renderBackground(guiGraphics)
guiGraphics.blit(AbstractContainerScreen.INVENTORY_LOCATION, width / 2, height / 2, 0, 0, 176, 166)
}
override fun removed() {
super.removed()
ProjectionShell.channel.sendToServer(ServerboundPSHLockReleasePacket(pos))
}
}

View File

@ -0,0 +1,70 @@
package quaedam.shell.network
import dev.architectury.networking.NetworkManager.PacketContext
import dev.architectury.utils.GameInstance
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import quaedam.Quaedam
import quaedam.shell.ProjectionShell
import quaedam.shell.ProjectionShellBlock
import quaedam.shell.ProjectionShellScreen
import java.util.function.Supplier
data class ClientboundPSHLockResultPacket(val pos: BlockPos, val result: Boolean) {
companion object {
init {
ProjectionShell.channel.register(
ClientboundPSHLockResultPacket::class.java,
ClientboundPSHLockResultPacket::encode,
::ClientboundPSHLockResultPacket,
ClientboundPSHLockResultPacket::apply
)
}
}
constructor(buf: FriendlyByteBuf) : this(buf.readBlockPos(), buf.readBoolean())
fun encode(buf: FriendlyByteBuf) {
buf.writeBlockPos(pos)
buf.writeBoolean(result)
}
fun apply(context: Supplier<PacketContext>) {
val ctx = context.get()
if (ctx.player.level().isClientSide) {
val client = GameInstance.getClient()
if (result) {
val level = ctx.player.level()
val block = level.getBlockState(pos).block
if (block is ProjectionShellBlock) {
ctx.queue {
try {
client.setScreen(
ProjectionShellScreen(
level,
pos,
block.getProjectionEffectForShell(level, pos)
)
)
} catch (e: Throwable) {
Quaedam.logger.error("Failed to open projection shell screen", e)
}
}
} else {
Quaedam.logger.warn("ClientboundPSHLockResultPacket with non-shell-provider block received")
}
} else {
ctx.queue {
client.setScreen(null)
client.gui.setOverlayMessage(
Component.translatable("quaedam.screen.projection_shell.lock_failed"),
false
)
}
}
}
}
}

View File

@ -0,0 +1,37 @@
package quaedam.shell.network
import dev.architectury.networking.NetworkManager.PacketContext
import dev.architectury.utils.GameInstance
import net.minecraft.network.chat.Component
import quaedam.shell.ProjectionShell
import quaedam.shell.ProjectionShellScreen
import java.util.function.Supplier
object ClientboundPSHLockRevokePacket {
init {
ProjectionShell.channel.register(
ClientboundPSHLockRevokePacket::class.java,
{ _, _ -> },
{ ClientboundPSHLockRevokePacket },
{ _, ctx -> apply(ctx) }
)
}
private fun apply(context: Supplier<PacketContext>) {
val ctx = context.get()
if (ctx.player.level().isClientSide) {
ctx.queue {
val client = GameInstance.getClient()
if (client.screen is ProjectionShellScreen) {
client.setScreen(null)
client.gui.setOverlayMessage(
Component.translatable("quaedam.screen.projection_shell.lock_revoked"),
false
)
}
}
}
}
}

View File

@ -0,0 +1,42 @@
package quaedam.shell.network
import dev.architectury.networking.NetworkManager.PacketContext
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import quaedam.shell.ProjectionShell
import quaedam.shell.ProjectionShellMutex
import java.util.function.Supplier
data class ServerboundPSHLockAcquirePacket(val pos: BlockPos) {
companion object {
init {
ProjectionShell.channel.register(
ServerboundPSHLockAcquirePacket::class.java,
ServerboundPSHLockAcquirePacket::encode,
::ServerboundPSHLockAcquirePacket,
ServerboundPSHLockAcquirePacket::apply
)
}
}
constructor(buf: FriendlyByteBuf) : this(buf.readBlockPos())
fun encode(buf: FriendlyByteBuf) {
buf.writeBlockPos(pos)
}
fun apply(context: Supplier<PacketContext>) {
val ctx = context.get()
if (!ctx.player.level().isClientSide) {
ctx.queue {
val player = ctx.player as ServerPlayer
val result = ProjectionShellMutex.tryLock(ctx.player.level() as ServerLevel, pos, player)
ProjectionShell.channel.sendToPlayer(player, ClientboundPSHLockResultPacket(pos, result))
}
}
}
}

View File

@ -0,0 +1,41 @@
package quaedam.shell.network
import dev.architectury.networking.NetworkManager.PacketContext
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import quaedam.shell.ProjectionShell
import quaedam.shell.ProjectionShellMutex
import java.util.function.Supplier
data class ServerboundPSHLockReleasePacket(val pos: BlockPos) {
companion object {
init {
ProjectionShell.channel.register(
ServerboundPSHLockReleasePacket::class.java,
ServerboundPSHLockReleasePacket::encode,
::ServerboundPSHLockReleasePacket,
ServerboundPSHLockReleasePacket::apply
)
}
}
constructor(buf: FriendlyByteBuf) : this(buf.readBlockPos())
fun encode(buf: FriendlyByteBuf) {
buf.writeBlockPos(pos)
}
fun apply(context: Supplier<PacketContext>) {
val ctx = context.get()
if (!ctx.player.level().isClientSide) {
ctx.queue {
val player = ctx.player as ServerPlayer
ProjectionShellMutex.release(ctx.player.level() as ServerLevel, pos, player)
}
}
}
}

View File

@ -5,5 +5,11 @@
"block.quaedam.swarm_projection": "Swarm Projection", "block.quaedam.swarm_projection": "Swarm Projection",
"block.quaedam.sound_projection": "Sound Projection", "block.quaedam.sound_projection": "Sound Projection",
"block.quaedam.noise_projection": "Noise Projection", "block.quaedam.noise_projection": "Noise Projection",
"entity.quaedam.projected_person": "Virtual Person" "entity.quaedam.projected_person": "Virtual Person",
"item.quaedam.projection_shell": "Projection Shell",
"quaedam.screen.projection_shell": "Projection Shell",
"quaedam.screen.projection_shell.lock_revoked": "Timeout! Connection Lost",
"quaedam.screen.projection_shell.lock_failed": "Permission denied!",
"quaedam.screen.projection_shell.save": "Save",
"quaedam.shell.skylight.factor": "Factor"
} }

View File

@ -5,5 +5,9 @@
"block.quaedam.swarm_projection": "人群投影", "block.quaedam.swarm_projection": "人群投影",
"block.quaedam.sound_projection": "声音投影", "block.quaedam.sound_projection": "声音投影",
"block.quaedam.noise_projection": "噪音投影", "block.quaedam.noise_projection": "噪音投影",
"entity.quaedam.projected_person": "虚拟个体" "entity.quaedam.projected_person": "虚拟个体",
"item.quaedam.projection_shell": "投影操作面板",
"quaedam.screen.projection_shell": "投影操作",
"quaedam.screen.projection_shell.lock_revoked": "超时!连接丢失",
"quaedam.screen.projection_shell.lock_failed": "正被使用"
} }

View File

@ -8,7 +8,8 @@
], ],
"mixins": [ "mixins": [
"MixinBedBlock", "MixinBedBlock",
"MixinBuiltInRegistries" "MixinBuiltInRegistries",
"MixinMinecraftServer"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1