synchronization and persistant state
This commit is contained in:
@@ -10,7 +10,7 @@ import org.neoflock.neocomputers.NeoComputers
|
||||
import java.util.function.Supplier
|
||||
import com.google.common.base.Suppliers
|
||||
|
||||
open class BaseBlock : Block(BlockBehaviour.Properties.of()) { // TODO: create a TieredBaseBlock class that extends this or something
|
||||
open class BaseBlock(properties: Properties = Properties.of()) : Block(properties) { // TODO: create a TieredBaseBlock class that extends this or something
|
||||
// val tier: Int
|
||||
|
||||
companion object Registry {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.neoflock.neocomputers.block
|
||||
|
||||
import net.minecraft.client.player.LocalPlayer
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.chat.ChatType
|
||||
import net.minecraft.network.chat.OutgoingChatMessage
|
||||
import net.minecraft.network.chat.PlayerChatMessage
|
||||
@@ -19,23 +22,18 @@ import org.neoflock.neocomputers.network.PowerRole
|
||||
import kotlin.math.min
|
||||
|
||||
open class CapacitorEntity(val capacity: Long, type: BlockEntityType<*>, pos: BlockPos, state: BlockState) : NodeBlockEntity(type, pos, state) {
|
||||
var amountStored: Long = 0
|
||||
|
||||
override val node = object : Networking.Node() {
|
||||
override fun getPowerRole() = PowerRole.STORAGE
|
||||
override fun getEnergy() = amountStored
|
||||
override fun getEnergyCapacity() = capacity
|
||||
override fun giveEnergy(amount: Long): Long {
|
||||
val given = min(amount, capacity - amountStored)
|
||||
amountStored += given
|
||||
return given
|
||||
}
|
||||
override var powerRole = PowerRole.STORAGE
|
||||
override var energyCapacity: Long = capacity
|
||||
}
|
||||
|
||||
override fun withdrawEnergy(amount: Long): Long {
|
||||
val taken = min(amount, amountStored)
|
||||
amountStored -= taken
|
||||
return taken
|
||||
}
|
||||
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
|
||||
node.energy = min(compoundTag.getLong("energy"), node.energyCapacity)
|
||||
}
|
||||
|
||||
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
|
||||
compoundTag.putLong("energy", node.energy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,15 +59,15 @@ class CapacitorBlock(val tier: Int) : NodeBlock() {
|
||||
player: Player,
|
||||
blockHitResult: BlockHitResult
|
||||
): InteractionResult {
|
||||
if(!level.isClientSide()) {
|
||||
val sp = player as ServerPlayer
|
||||
if(level.isClientSide()) {
|
||||
val p = player as LocalPlayer
|
||||
val ent = level.getBlockEntity(blockPos)
|
||||
if(ent is CapacitorEntity) {
|
||||
if(sp.isCrouching) ent.amountStored++
|
||||
val msg = PlayerChatMessage.system("energy: ${ent.amountStored} / ${ent.capacity} (${ent.computeEdges().size} edges, ${ent.node.getReachable().size} connected)")
|
||||
sp.sendChatMessage(OutgoingChatMessage.create(msg), false, ChatType.bind(ChatType.CHAT, player))
|
||||
if(p.isCrouching) ent.node.giveEnergy(1)
|
||||
val msg = PlayerChatMessage.system("energy: ${ent.node.energy} / ${ent.capacity} (${ent.computeEdges().size} edges, ${ent.node.getReachable().size} connected)")
|
||||
p.sendSystemMessage(OutgoingChatMessage.create(msg).content())
|
||||
}
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
return InteractionResult.SUCCESS
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,30 @@ package org.neoflock.neocomputers.block
|
||||
|
||||
import dev.architectury.registry.menu.MenuRegistry
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.particles.ParticleTypes
|
||||
import net.minecraft.network.chat.ChatType
|
||||
import net.minecraft.network.chat.OutgoingChatMessage
|
||||
import net.minecraft.network.chat.PlayerChatMessage
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.sounds.SoundEvent
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.sounds.SoundSource
|
||||
import net.minecraft.util.RandomSource
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.EntityBlock
|
||||
import net.minecraft.world.level.block.FurnaceBlock
|
||||
import net.minecraft.world.level.block.SoundType
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty
|
||||
import net.minecraft.world.level.storage.loot.LootParams
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import org.neoflock.neocomputers.entity.BlockEntities
|
||||
import org.neoflock.neocomputers.entity.SolarGeneratorBlockEntity
|
||||
@@ -22,7 +36,19 @@ class SolarGeneratorBlock : NodeBlock(), EntityBlock {
|
||||
}
|
||||
|
||||
// TODO: make it glow when burning
|
||||
class CombustionGeneratorBlock : NodeBlock(), EntityBlock {
|
||||
class CombustionGeneratorBlock : NodeBlock, EntityBlock {
|
||||
companion object {
|
||||
val ACTIVE = BooleanProperty.create("active")
|
||||
|
||||
fun getLuminance(blockState: BlockState): Int {
|
||||
return if(blockState.getValue(ACTIVE)) 5 else 0
|
||||
}
|
||||
}
|
||||
|
||||
constructor(): super(Properties.of().sound(SoundType.STONE).lightLevel(CombustionGeneratorBlock::getLuminance)) {
|
||||
registerDefaultState(defaultBlockState().setValue(ACTIVE, false))
|
||||
}
|
||||
|
||||
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = CombustionGeneratorBlockEntity(blockPos, blockState).initNetworking()
|
||||
|
||||
override fun useWithoutItem(
|
||||
@@ -35,8 +61,25 @@ class CombustionGeneratorBlock : NodeBlock(), EntityBlock {
|
||||
if(!level.isClientSide()) {
|
||||
val sp = player as ServerPlayer
|
||||
val ent = level.getBlockEntity(blockPos, BlockEntities.COMBUSTGEN_ENTITY.get()).get()
|
||||
NodeSynchronizer.registerPlayerScreen(sp, ent)
|
||||
MenuRegistry.openMenu(sp, ent)
|
||||
}
|
||||
return InteractionResult.SUCCESS
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
|
||||
builder.add(ACTIVE)
|
||||
}
|
||||
|
||||
override fun animateTick(blockState: BlockState, level: Level, blockPos: BlockPos, randomSource: RandomSource) {
|
||||
if(blockState.getValue(ACTIVE)) {
|
||||
if(randomSource.nextDouble() < 0.1) level.playSound(null, blockPos, SoundEvents.FURNACE_FIRE_CRACKLE, SoundSource.AMBIENT)
|
||||
|
||||
val x = blockPos.x.toDouble()
|
||||
val y = blockPos.y.toDouble()
|
||||
val z = blockPos.z.toDouble()
|
||||
|
||||
level.addParticle(ParticleTypes.SMOKE, x+0.5, y+0.5, z+0.5, 0.0, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
package org.neoflock.neocomputers.block
|
||||
|
||||
import dev.architectury.networking.NetworkManager
|
||||
import io.netty.buffer.Unpooled
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.network.codec.StreamCodec
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
@@ -11,8 +20,73 @@ import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import org.neoflock.neocomputers.NeoComputers
|
||||
import org.neoflock.neocomputers.network.Networking
|
||||
|
||||
object NodeSynchronizer {
|
||||
class StatePayload(var blockPos: BlockPos, var buffer: FriendlyByteBuf): CustomPacketPayload {
|
||||
companion object {
|
||||
val NODE_SYNC_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "node_sync")
|
||||
val TYPE = CustomPacketPayload.Type<StatePayload>(NODE_SYNC_ID)
|
||||
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, StatePayload> {
|
||||
override fun decode(buf: RegistryFriendlyByteBuf): StatePayload {
|
||||
val blockPos = buf.readBlockPos()
|
||||
val buffer = FriendlyByteBuf(buf.copy(buf.readerIndex(), buf.readableBytes()))
|
||||
return StatePayload(blockPos, buffer)
|
||||
}
|
||||
|
||||
override fun encode(buf: RegistryFriendlyByteBuf, payload: StatePayload) {
|
||||
buf.writeBlockPos(payload.blockPos)
|
||||
buf.writeBytes(payload.buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun type() = TYPE
|
||||
}
|
||||
|
||||
class ScreenPayload(var entityTypeWireID: String, var buffer: FriendlyByteBuf): CustomPacketPayload {
|
||||
companion object {
|
||||
val SCREEN_SYNC_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen_sync")
|
||||
val TYPE = CustomPacketPayload.Type<ScreenPayload>(SCREEN_SYNC_ID)
|
||||
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, ScreenPayload> {
|
||||
override fun decode(buf: RegistryFriendlyByteBuf): ScreenPayload {
|
||||
val id = buf.readByteArray().decodeToString()
|
||||
val buffer = FriendlyByteBuf(buf.copy(buf.readerIndex(), buf.readableBytes()))
|
||||
return ScreenPayload(id, buffer)
|
||||
}
|
||||
|
||||
override fun encode(buf: RegistryFriendlyByteBuf, payload: ScreenPayload) {
|
||||
buf.writeByteArray(payload.entityTypeWireID.encodeToByteArray())
|
||||
buf.writeBytes(payload.buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun type() = TYPE
|
||||
}
|
||||
|
||||
val screenMap = mutableMapOf<ServerPlayer, NodeBlockEntity>()
|
||||
|
||||
fun playerScreenClosed(player: ServerPlayer) {
|
||||
screenMap.remove(player)
|
||||
}
|
||||
|
||||
fun nodeTypeToWireID(nodeType: BlockEntityType<*>): String = nodeType.javaClass.canonicalName
|
||||
|
||||
fun registerPlayerScreen(player: ServerPlayer, entity: NodeBlockEntity) {
|
||||
screenMap[player] = entity
|
||||
}
|
||||
|
||||
fun syncScreens() {
|
||||
for((player, ent) in screenMap) {
|
||||
val buf = FriendlyByteBuf(Unpooled.buffer())
|
||||
ent.encodeScreenData(player, buf)
|
||||
NetworkManager.sendToPlayer(player, ScreenPayload(nodeTypeToWireID(ent.type), buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : BlockEntity(blockEntityType, blockPos, blockState) {
|
||||
abstract val node: Networking.Node
|
||||
|
||||
@@ -21,6 +95,27 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
|
||||
return this
|
||||
}
|
||||
|
||||
// runs on the server, meant to encode state to send to all players
|
||||
open fun encodeDownstreamData(packet: FriendlyByteBuf) {
|
||||
packet.writeUUID(node.address)
|
||||
packet.writeLong(node.energy)
|
||||
packet.writeLong(node.energyCapacity)
|
||||
packet.writeEnum(node.reachability)
|
||||
packet.writeEnum(node.powerRole)
|
||||
}
|
||||
|
||||
// runs on the client, meant to decode server state packets to synchronize client state
|
||||
open fun syncWithUpstream(packet: FriendlyByteBuf) {
|
||||
node.address = packet.readUUID()
|
||||
node.energy = packet.readLong()
|
||||
node.energyCapacity = packet.readLong()
|
||||
node.reachability = packet.readEnum(node.reachability.javaClass)
|
||||
node.powerRole = packet.readEnum(node.powerRole.javaClass)
|
||||
}
|
||||
|
||||
// Encodes data meant for the associated screen of a player
|
||||
open fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {}
|
||||
|
||||
private var stateIsDirty = true
|
||||
|
||||
open fun getNeighbourEntities(): List<BlockEntity> {
|
||||
@@ -36,7 +131,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
|
||||
return subpos.mapNotNull { pos -> level?.getBlockEntity(pos) }
|
||||
}
|
||||
|
||||
fun computeEdges(): Set<NodeBlockEntity> {
|
||||
open fun computeEdges(): Set<NodeBlockEntity> {
|
||||
val s = mutableSetOf<NodeBlockEntity>()
|
||||
val neighbours = getNeighbourEntities()
|
||||
for(neighbour in neighbours) {
|
||||
@@ -47,13 +142,21 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
|
||||
return s
|
||||
}
|
||||
|
||||
fun invalidateNodeState() {
|
||||
open fun invalidateNodeState() {
|
||||
stateIsDirty = true
|
||||
}
|
||||
|
||||
fun needsSynchronization() = stateIsDirty
|
||||
|
||||
open fun tickNode() {
|
||||
open fun tickNode(level: Level) {
|
||||
if(!level.isClientSide) {
|
||||
val l = level as ServerLevel
|
||||
val packetBuf = FriendlyByteBuf(Unpooled.buffer())
|
||||
encodeDownstreamData(packetBuf)
|
||||
l.players().forEach {
|
||||
if(it.level().isLoaded(blockPos)) NetworkManager.sendToPlayer(it, NodeSynchronizer.StatePayload(blockPos, packetBuf))
|
||||
}
|
||||
}
|
||||
if(!stateIsDirty) return
|
||||
stateIsDirty = false
|
||||
computeEdges().forEach {
|
||||
@@ -73,7 +176,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
|
||||
}
|
||||
}
|
||||
|
||||
abstract class NodeBlock: BaseBlock(), EntityBlock {
|
||||
abstract class NodeBlock(properties: Properties = Properties.of()): BaseBlock(properties), EntityBlock {
|
||||
override fun <T : BlockEntity> getTicker(
|
||||
level: Level,
|
||||
blockState: BlockState,
|
||||
@@ -82,7 +185,7 @@ abstract class NodeBlock: BaseBlock(), EntityBlock {
|
||||
return object : BlockEntityTicker<T> {
|
||||
override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) {
|
||||
if(blockEntity !is NodeBlockEntity) return;
|
||||
blockEntity.tickNode()
|
||||
blockEntity.tickNode(level)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user