feat: bare projection shell

This commit is contained in:
2023-07-21 17:56:28 +08:00
parent 899eac954e
commit d88f110fe5
18 changed files with 489 additions and 12 deletions
@@ -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;
}
}
@@ -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();
}
@@ -17,6 +17,7 @@ import quaedam.projection.misc.SkylightProjection
import quaedam.projection.misc.SoundProjection
import quaedam.projection.swarm.SwarmProjection
import quaedam.projector.Projector
import quaedam.shell.ProjectionShell
object Quaedam {
@@ -50,6 +51,7 @@ object Quaedam {
NoiseProjection
ProjectionCommand
SimpleProjectionUpdate
ProjectionShell
creativeModeTabs.register()
items.register()
@@ -8,10 +8,12 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.block.EntityBlock
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.ProjectionShellBlock
import quaedam.utils.sendBlockUpdated
abstract class EntityProjectionBlock<P : ProjectionEffect>(properties: Properties = createProperties()) :
ProjectionBlock<P>(properties), EntityBlock {
ProjectionBlock<P>(properties), EntityBlock, ProjectionShellBlock {
companion object {
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())
}
}
@@ -1,20 +1,15 @@
package quaedam.projection.misc
import net.minecraft.core.BlockPos
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.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.projection.EntityProjectionBlock
import quaedam.projection.ProjectionEffect
import quaedam.projection.ProjectionEffectType
import quaedam.projection.SimpleProjectionEntity
import quaedam.shell.ProjectionEffectShell
import quaedam.shell.buildProjectionEffectShell
import kotlin.math.min
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 {
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)
}
}
@@ -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
@@ -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
}
}
@@ -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)
}
@@ -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
}
}
@@ -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)
}
@@ -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))
}
}
@@ -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
)
}
}
}
}
}
@@ -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
)
}
}
}
}
}
@@ -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))
}
}
}
}
@@ -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)
}
}
}
}
@@ -5,5 +5,11 @@
"block.quaedam.swarm_projection": "Swarm Projection",
"block.quaedam.sound_projection": "Sound 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"
}
@@ -5,5 +5,9 @@
"block.quaedam.swarm_projection": "人群投影",
"block.quaedam.sound_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": "正被使用"
}
@@ -8,7 +8,8 @@
],
"mixins": [
"MixinBedBlock",
"MixinBuiltInRegistries"
"MixinBuiltInRegistries",
"MixinMinecraftServer"
],
"injectors": {
"defaultRequire": 1