NodeBlockEntity is dead, long live SingleDeviceBlockEntity + cables function

This commit is contained in:
2026-04-28 17:16:33 +03:00
parent 464584877c
commit 43255b1caf
28 changed files with 495 additions and 537 deletions

View File

@@ -27,7 +27,7 @@ import net.minecraft.world.phys.shapes.VoxelShape
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.CableEntity
class CableBlock() : BaseBlock(Properties.of()), EntityBlock {
class CableBlock() : DeviceBlock(Properties.of()), EntityBlock {
companion object {
val NORTH = BooleanProperty.create("north")
val EAST = BooleanProperty.create("east")
@@ -96,12 +96,20 @@ class CableBlock() : BaseBlock(Properties.of()), EntityBlock {
fun shouldConnect(pos: BlockPos, npos: BlockPos, level: Level): Boolean {
val ent = level.getBlockEntity(npos)
val blockState = level.getBlockState(pos)
val theirState = level.getBlockState(npos)
return ent is NodeBlockEntity || (ent is CableEntity &&
(level.getBlockState(npos).getValue(COLOR).equals(blockState.getValue(COLOR)) ||
blockState.getValue(COLOR).equals(DyeColor.LIGHT_GRAY) ||
level.getBlockState(npos).getValue(COLOR).equals(DyeColor.LIGHT_GRAY)))
// val state: BlockState? = (ent is CableEntity && (level.getBlockState(neighborPos).getValue(COLOR).equals(state.getValue(COLOR)) || state.getValue(COLOR).equals(DyeColor.LIGHT_GRAY))
val universal = DyeColor.LIGHT_GRAY
if(ent is CableEntity) {
val ourColor = blockState.getValue(COLOR)
val theirColor = theirState.getValue(COLOR)
if(ourColor.equals(universal)) return true
if(theirColor.equals(universal)) return true
if(ourColor.equals(theirColor)) return true
return false
}
return ent is DeviceBlockEntity
}
}
@@ -130,9 +138,7 @@ class CableBlock() : BaseBlock(Properties.of()), EntityBlock {
.add(COLOR))
}
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity? {
return CableEntity(pos, state)
}
override fun newBlockEntity(pos: BlockPos, state: BlockState) = CableEntity(pos, state)
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape? {
val idx = calcIdx(state.getValue(NORTH), state.getValue(SOUTH), state.getValue(EAST), state.getValue(WEST), state.getValue(UP), state.getValue(DOWN))

View File

@@ -5,6 +5,7 @@ import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.OutgoingChatMessage
import net.minecraft.network.chat.PlayerChatMessage
import net.minecraft.server.level.ServerPlayer
@@ -25,6 +26,16 @@ open class CapacitorEntity(val capacity: Long, type: BlockEntityType<*>, pos: Bl
val deviceNode = object : DeviceNode() {
override var powerRole = PowerRole.STORAGE
override var energyCapacity: Long = capacity
override fun writeFullStateCommit(buf: FriendlyByteBuf) {
super.writeFullStateCommit(buf)
buf.writeVarLong(energy)
}
override fun processCommit(buf: FriendlyByteBuf) {
super.processCommit(buf)
energy = buf.readVarLong()
}
}
// TODO: cache list
@@ -46,7 +57,7 @@ class CapacitorEntityTier1(pos: BlockPos, state: BlockState): CapacitorEntity(20
class CapacitorEntityTier2(pos: BlockPos, state: BlockState): CapacitorEntity(50000, BlockEntities.CAPACITOR2_ENTITY.get(), pos, state)
class CapacitorEntityTier3(pos: BlockPos, state: BlockState): CapacitorEntity(100000, BlockEntities.CAPACITOR3_ENTITY.get(), pos, state)
class CapacitorBlock(val tier: Int) : NodeBlock() {
class CapacitorBlock(val tier: Int) : DeviceBlock() {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity {
val cap: CapacitorEntity = when(tier) {
1 -> CapacitorEntityTier1(blockPos, blockState)
@@ -54,7 +65,7 @@ class CapacitorBlock(val tier: Int) : NodeBlock() {
3 -> CapacitorEntityTier3(blockPos, blockState)
else -> throw UnsupportedOperationException("unsupported tier: $tier")
}
return cap.initNetworking()
return cap
}
override fun useWithoutItem(
@@ -64,13 +75,12 @@ class CapacitorBlock(val tier: Int) : NodeBlock() {
player: Player,
blockHitResult: BlockHitResult
): InteractionResult {
if(!level.isClientSide()) {
val p = player as ServerPlayer
if(level.isClientSide()) {
val ent = level.getBlockEntity(blockPos)
if(ent is CapacitorEntity) {
if(p.isCrouching) ent.deviceNode.giveEnergy(1)
val msg = PlayerChatMessage.system("energy: ${ent.deviceNode.energy} / ${ent.capacity} (${ent.deviceNode.connections.size} connections, ${ent.deviceNode.getReachable().size} connected)")
p.sendSystemMessage(OutgoingChatMessage.create(msg).content())
if(player.isCrouching) ent.deviceNode.giveEnergy(1)
val msg = PlayerChatMessage.system("energy: ${ent.deviceNode.energy} / ${ent.capacity} (${ent.deviceNode.connections.size} connections, ${ent.deviceNode.getReachable().size} reachable)")
player.sendSystemMessage(OutgoingChatMessage.create(msg).content())
}
}
return InteractionResult.SUCCESS

View File

@@ -31,9 +31,10 @@ import org.neoflock.neocomputers.block.CombustionGeneratorBlock.Companion.COMBUS
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.CaseBlockEntity
import org.neoflock.neocomputers.entity.MachineEntity
import org.neoflock.neocomputers.network.NodeSynchronizer
import org.neoflock.neocomputers.sounds.Sounds
class CaseBlock() : NodeBlock(Properties.of().sound(SoundType.METAL).lightLevel(CaseBlock::getLuminance)) { // placeholder stuff
class CaseBlock() : DeviceBlock(Properties.of().sound(SoundType.METAL).lightLevel(CaseBlock::getLuminance)) { // placeholder stuff
companion object {
val FACING: EnumProperty<Direction> = EnumProperty.create<Direction>("facing", Direction::class.java)
val COMPUTER_RUNNING = BooleanProperty.create("running")!!
@@ -47,7 +48,7 @@ class CaseBlock() : NodeBlock(Properties.of().sound(SoundType.METAL).lightLevel(
registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(COMPUTER_RUNNING, false))
}
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState) = CaseBlockEntity(blockPos, blockState).initNetworking()
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState) = CaseBlockEntity(blockPos, blockState)
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(COMPUTER_RUNNING)
@@ -115,7 +116,7 @@ class CaseBlock() : NodeBlock(Properties.of().sound(SoundType.METAL).lightLevel(
} else {
// Open menu
MenuRegistry.openMenu(player as ServerPlayer, ent)
NodeSynchronizer.registerPlayerScreen(player, ent)
NodeSynchronizer.registerPlayerScreen(player, ent.deviceNode)
}
}
return InteractionResult.SUCCESS

View File

@@ -1,7 +1,11 @@
package org.neoflock.neocomputers.block
import dev.architectury.networking.NetworkManager
import io.netty.buffer.Unpooled
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
@@ -11,9 +15,18 @@ import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.NodeSynchronizer
abstract class SingleDeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): DeviceBlockEntity(type, pos, state) {
abstract val deviceNode: DeviceNode
override fun getDeviceNodes() = listOf(deviceNode)
override fun getNodeFromSide(directionToRequester: Direction): DeviceNode? = deviceNode
}
abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): BlockEntity(type, pos, state) {
val connetionsInDir = MutableList(Direction.entries.size) { HashSet<DeviceNode>() }
val connetionsInDir = MutableList<DeviceNode?>(Direction.entries.size) { null }
var alreadySetup = false
abstract fun getDeviceNodes(): List<DeviceNode>
@@ -22,57 +35,85 @@ abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state:
// so it is Direction.UP if we asked from the one on the top side.
abstract fun getNodeFromSide(directionToRequester: Direction): DeviceNode?
open fun processCommits(commits: Iterable<FriendlyByteBuf>) {
val devs = getDeviceNodes()
for (buf in commits) {
val idx = buf.readVarInt()
if(idx >= 0 && idx < devs.size) {
devs[idx].processCommit(buf)
}
}
}
open fun initNetworking(): DeviceBlockEntity {
getDeviceNodes().forEach { Networking.addNode(it) }
Direction.entries.forEach { handleConnectionsFor(it) }
if(hasLevel()) {
alreadySetup = true
Networking.addNodes(getDeviceNodes())
Direction.entries.forEach { handleConnectionsFor(it) }
}
return this
}
open fun getCurrentlyConnectedNodesIn(direction: Direction): HashSet<DeviceNode> {
// Cables are 1 node
open fun getCurrentlyConnectedNodeIn(direction: Direction): DeviceNode? {
val ent = level?.getBlockEntity(blockPos.relative(direction))
val connected = HashSet<DeviceNode>()
if(ent is DeviceBlockEntity) {
val node = ent.getNodeFromSide(direction.opposite)
if(node != null) connected.add(node)
return ent.getNodeFromSide(direction.opposite)
}
return connected
return null
}
// TODO: rethink this shi so sharing a node on 2 different sides doesn't make connections require mutually exclusive conditions
// TODO: actually like, rethink the whole class so far
open fun handleConnectionsFor(direction: Direction) {
// refuse connections on no node to reduce CPU load
val node = getNodeFromSide(direction.opposite) ?: return
val old = connetionsInDir[direction.ordinal]
val now = getCurrentlyConnectedNodesIn(direction)
val now = getCurrentlyConnectedNodeIn(direction)
// TODO: optimize this hellscape
val toKill = HashSet<DeviceNode>()
old.forEach {
if(it !in now) toKill.add(it)
}
toKill.forEach { node.disconnectFrom(it) }
now.forEach {
if(it !in old) node.connectTo(it)
if(old?.address != now?.address) {
if(old != null) node.disconnectFrom(old)
if(now != null) node.connectTo(now)
}
connetionsInDir[direction.ordinal] = now
}
// TODO: optimize this sometime before our test computers melt
open fun tickDevice() {
open fun tickDevice(level: Level) {
// Handles device connections and sync here
// Process connections
Direction.entries.forEach {
handleConnectionsFor(it)
// we do it like this because stinky MC will call stuff before world is fully setup
// and then not notify us of neighbour changes
// this is because MC is considered shit
if(!alreadySetup) {
initNetworking()
}
}
open fun sendCommitsToClient(level: Level) {
if(level is ServerLevel) {
// synchronization!
val commits = mutableListOf<FriendlyByteBuf>()
val devs = getDeviceNodes()
for((i, dev) in devs.withIndex()) {
// TODO: use dev.outOfSync to only set commits if something changed, and allow client to request the commits (securely)
dev.outOfSync = false
val buf = FriendlyByteBuf(Unpooled.buffer())
buf.writeVarInt(i)
dev.writeFullStateCommit(buf)
commits.addLast(buf)
}
if(commits.isNotEmpty()) {
level.players().forEach {
val dist = it.position().distanceTo(blockPos.center)
if(dist < 100) NetworkManager.sendToPlayer(it, NodeSynchronizer.DeviceBlockStatePayload(blockPos, commits))
}
}
}
}
override fun setRemoved() {
super.setRemoved()
getDeviceNodes().forEach { Networking.removeNode(it) }
alreadySetup = false
Networking.removeNodes(getDeviceNodes())
}
}
@@ -85,7 +126,8 @@ abstract class DeviceBlock(properties: Properties = Properties.of()): BaseBlock(
return object : BlockEntityTicker<T> {
override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T & Any) {
if(blockEntity !is DeviceBlockEntity) return
blockEntity.tickDevice()
blockEntity.tickDevice(level)
blockEntity.sendCommitsToClient(level)
}
}
}

View File

@@ -23,13 +23,14 @@ import net.minecraft.world.phys.BlockHitResult
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.SolarGeneratorBlockEntity
import org.neoflock.neocomputers.entity.CombustionGeneratorBlockEntity
import org.neoflock.neocomputers.network.NodeSynchronizer
class SolarGeneratorBlock : NodeBlock(), EntityBlock {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = SolarGeneratorBlockEntity(blockPos, blockState).initNetworking()
class SolarGeneratorBlock : DeviceBlock(), EntityBlock {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = SolarGeneratorBlockEntity(blockPos, blockState)
}
// TODO: make it glow when burning
class CombustionGeneratorBlock : NodeBlock, EntityBlock {
class CombustionGeneratorBlock : DeviceBlock, EntityBlock {
companion object {
val COMBUSTGEN_ACTIVE = BooleanProperty.create("active")
@@ -42,7 +43,7 @@ class CombustionGeneratorBlock : NodeBlock, EntityBlock {
registerDefaultState(defaultBlockState().setValue(COMBUSTGEN_ACTIVE, false))
}
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = CombustionGeneratorBlockEntity(blockPos, blockState).initNetworking()
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = CombustionGeneratorBlockEntity(blockPos, blockState)
override fun useWithoutItem(
blockState: BlockState,
@@ -54,7 +55,7 @@ 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)
NodeSynchronizer.registerPlayerScreen(sp, ent.deviceNode)
MenuRegistry.openMenu(sp, ent)
}
return InteractionResult.SUCCESS

View File

@@ -1,304 +0,0 @@
package org.neoflock.neocomputers.block
import dev.architectury.networking.NetworkManager
import io.netty.buffer.Unpooled
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
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.level.Level
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.EntityBlock
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
import org.neoflock.neocomputers.network.DeviceNode
import java.time.Duration
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
}
class ScreenDataPayload(var entityTypeWireID: String, var buffer: FriendlyByteBuf): CustomPacketPayload {
companion object {
val SCREEN_DATA_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen_data")
val TYPE = CustomPacketPayload.Type<ScreenDataPayload>(SCREEN_DATA_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, ScreenDataPayload> {
override fun decode(buf: RegistryFriendlyByteBuf): ScreenDataPayload {
val id = buf.readByteArray().decodeToString()
val buffer = FriendlyByteBuf(buf.copy(buf.readerIndex(), buf.readableBytes()))
return ScreenDataPayload(id, buffer)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: ScreenDataPayload) {
buf.writeByteArray(payload.entityTypeWireID.encodeToByteArray())
buf.writeBytes(payload.buffer)
}
}
}
override fun type() = TYPE
}
class BeepDataPayload(val pos: BlockPos, val pattern: String, val freq: Int, val duration: Duration, val volume: Double): CustomPacketPayload {
companion object {
val BEEP_DATA_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "beep_data")
val TYPE = CustomPacketPayload.Type<BeepDataPayload>(BEEP_DATA_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, BeepDataPayload> {
override fun decode(buf: RegistryFriendlyByteBuf): BeepDataPayload {
val pos = buf.readBlockPos()
val pattern = buf.readUtf()
val freq = buf.readVarInt()
val duration = buf.readVarLong()
val volume = buf.readDouble()
return BeepDataPayload(pos, pattern, freq, Duration.ofMillis(duration), volume)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: BeepDataPayload) {
buf.writeBlockPos(payload.pos)
buf.writeUtf(payload.pattern)
buf.writeVarInt(payload.freq)
buf.writeVarLong(payload.duration.toMillis())
buf.writeDouble(payload.volume)
}
}
}
override fun type() = TYPE
}
val screenMap = HashMap<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))
}
}
fun sendScreenInteraction(friendlyByteBuf: FriendlyByteBuf) {
NetworkManager.sendToServer(ScreenDataPayload("", friendlyByteBuf))
}
fun emitBeep(level: Level, beepDataPayload: BeepDataPayload) {
if(level is ServerLevel) {
level.players().forEach {
NetworkManager.sendToPlayer(it, beepDataPayload)
}
}
}
}
abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : BlockEntity(blockEntityType, blockPos, blockState) {
abstract val deviceNode: DeviceNode
fun initNetworking(): NodeBlockEntity {
Networking.addNode(deviceNode)
invalidateNodeState()
return this
}
// runs on the server, meant to encode state to send to all players
open fun encodeDownstreamData(packet: FriendlyByteBuf) {
packet.writeUUID(deviceNode.address)
packet.writeLong(deviceNode.energy)
packet.writeLong(deviceNode.energyCapacity)
packet.writeEnum(deviceNode.reachability)
packet.writeEnum(deviceNode.powerRole)
}
// runs on the client, meant to decode server state packets to synchronize client state
open fun syncWithUpstream(packet: FriendlyByteBuf) {
Networking.changeNodeAddress(deviceNode, packet.readUUID())
deviceNode.energy = packet.readLong()
deviceNode.energyCapacity = packet.readLong()
deviceNode.reachability = packet.readEnum(deviceNode.reachability.javaClass)
deviceNode.powerRole = packet.readEnum(deviceNode.powerRole.javaClass)
}
// Encodes data meant for the associated screen of a player
open fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {}
open fun processScreenInteraction(player: ServerPlayer, packet: FriendlyByteBuf) {}
private var stateIsDirty = true
open fun getNeighbourEntities(): List<BlockEntity> {
val subpos = listOf(
blockPos.offset(0, 0, 1),
blockPos.offset(0, 0, -1),
blockPos.offset(0, 1, 0),
blockPos.offset(0, -1, 0),
blockPos.offset(1, 0, 0),
blockPos.offset(-1, 0, 0),
)
return subpos.mapNotNull { pos -> level?.getBlockEntity(pos) }
}
open fun computeEdges(): Set<NodeBlockEntity> {
val s = mutableSetOf<NodeBlockEntity>()
val neighbours = getNeighbourEntities()
for(neighbour in neighbours) {
if(neighbour is NodeBlockEntity) s.add(neighbour);
// TODO: handle cable entities
}
s.remove(this)
return s
}
open fun invalidateNodeState() {
stateIsDirty = true
}
open fun needsSynchronization() = stateIsDirty
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 {
deviceNode.connectTo(it.deviceNode)
}
}
override fun setChanged() {
invalidateNodeState()
computeEdges().forEach { it.invalidateNodeState() }
super.setChanged()
}
override fun setRemoved() {
super.setRemoved()
Networking.removeNode(deviceNode)
}
override fun clearRemoved() {
super.clearRemoved()
initNetworking()
}
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider)
invalidateNodeState()
computeEdges().forEach { it.invalidateNodeState() }
}
}
abstract class NodeBlock(properties: Properties = Properties.of()): BaseBlock(properties), EntityBlock {
override fun <T : BlockEntity> getTicker(
level: Level,
blockState: BlockState,
blockEntityType: BlockEntityType<T>
): BlockEntityTicker<T>? {
return object : BlockEntityTicker<T> {
override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) {
if(blockEntity !is NodeBlockEntity) return
if(Networking.getNode(blockEntity.deviceNode.address) == null) blockEntity.initNetworking()
blockEntity.tickNode(level)
}
}
}
override fun onPlace(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
blockState2: BlockState,
bl: Boolean
) {
super.onPlace(blockState, level, blockPos, blockState2, bl)
if(!level.isClientSide) {
val ent = level.getBlockEntity(blockPos)
if(ent is NodeBlockEntity) {
ent.invalidateNodeState()
ent.computeEdges().forEach { it.invalidateNodeState() }
}
level.updateNeighborsAt(blockPos, this)
}
}
override fun neighborChanged(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
block: Block,
blockPos2: BlockPos,
bl: Boolean
) {
super.neighborChanged(blockState, level, blockPos, block, blockPos2, bl)
if(!level.isClientSide) {
val ent = level.getBlockEntity(blockPos)
if(ent is NodeBlockEntity) {
ent.invalidateNodeState()
}
}
}
}

View File

@@ -15,7 +15,7 @@ import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.DeviceNode
class RedstoneIOEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEntity(BlockEntities.REDSTONEIO_ENTITY.get(), blockPos, blockState) {
class RedstoneIOEntity(blockPos: BlockPos, blockState: BlockState): SingleDeviceBlockEntity(BlockEntities.REDSTONEIO_ENTITY.get(), blockPos, blockState) {
val redstoneIn = Array<Int>(Direction.entries.size) {0}
val redstoneOut = Array<Int>(Direction.entries.size) {0}
@@ -44,7 +44,7 @@ class RedstoneIOEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnt
}
}
class RedstoneIOBlock(): NodeBlock(Properties.of().isRedstoneConductor { state, getter, pos -> true }) {
class RedstoneIOBlock(): DeviceBlock(Properties.of().isRedstoneConductor { state, getter, pos -> true }) {
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = RedstoneIOEntity(blockPos, blockState)
fun getRedstoneIO(level: BlockGetter, blockPos: BlockPos): RedstoneIOEntity? {

View File

@@ -26,23 +26,19 @@ import org.neoflock.neocomputers.entity.ScreenEntity
import org.neoflock.neocomputers.gui.menu.ScreenMenu
import kotlin.math.abs
import kotlin.math.max
import org.neoflock.neocomputers.network.NodeSynchronizer
class ScreenBlock() : NodeBlock() {
class ScreenBlock() : DeviceBlock() {
companion object {
val FACING_HORIZ: EnumProperty<Direction> = EnumProperty.create<Direction>("facing_horiz", Direction::class.java)
val FACING_VERTI: IntegerProperty = IntegerProperty.create("facing_verti", 0, 2) // "Integer" property doesnt accept values below 0, also death to enums, long live magic numbers
val ENERGY: Long = 5
}
init {
registerDefaultState(stateDefinition.any().setValue(FACING_HORIZ, Direction.NORTH).setValue(FACING_VERTI, 1))
}
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity? {
val scr = ScreenEntity(blockPos, blockState)
scr.initNetworking()
return scr
}
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState) = ScreenEntity(blockPos, blockState)
override fun useWithoutItem(
blockState: BlockState,
@@ -52,14 +48,9 @@ class ScreenBlock() : NodeBlock() {
blockHitResult: BlockHitResult
): InteractionResult {
if(!level.isClientSide) {
val screenState = level.getBlockEntity(blockPos, BlockEntities.SCREEN_ENTITY.get()).get()
if(!screenState.deviceNode.consumeEnergy(ENERGY)) {
player.sendSystemMessage(Component.literal("Not enough power."))
return InteractionResult.SUCCESS
};
val sp = player as ServerPlayer
val ent = level.getBlockEntity(blockPos, BlockEntities.SCREEN_ENTITY.get()).get()
NodeSynchronizer.registerPlayerScreen(sp, ent)
NodeSynchronizer.registerPlayerScreen(sp, ent.deviceNode)
MenuRegistry.openExtendedMenu(sp, object : ExtendedMenuProvider {
override fun getDisplayName(): Component = Component.literal("SCREEEEEN!")
override fun createMenu(i: Int, inventory: Inventory, player: Player): AbstractContainerMenu {