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

@@ -1,7 +1,5 @@
package org.neoflock.neocomputers
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.VertexFormat
import dev.architectury.event.events.client.ClientLifecycleEvent
import dev.architectury.event.events.common.LifecycleEvent
import dev.architectury.event.events.common.PlayerEvent
@@ -15,24 +13,20 @@ import org.neoflock.neocomputers.gui.menu.Menus
import dev.architectury.utils.Env
import dev.architectury.utils.EnvExecutor
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.server.level.ServerPlayer
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.block.NodeSynchronizer
import org.neoflock.neocomputers.gui.buffer.BufferRenderer
import org.neoflock.neocomputers.block.DeviceBlockEntity
import org.neoflock.neocomputers.gui.render.ScreenRenderer
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.item.GPUCard
import org.neoflock.neocomputers.item.Items
import org.neoflock.neocomputers.item.Tabs
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.NodeSynchronizer
import org.neoflock.neocomputers.sounds.Sounds
import org.neoflock.neocomputers.utils.FontProvider
import org.neoflock.neocomputers.utils.GenericContainerScreen
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.nio.Buffer
object NeoComputers {
const val MODID: String = "neocomputers"
@@ -58,7 +52,7 @@ object NeoComputers {
Tabs.TABS.register()
Sounds.SOUNDS.register()
ComponentRoles.mapDefaultTextures()
// i dont know why architectury wants two lambdas but whatever
// I don't know why architectury wants two lambdas but whatever
EnvExecutor.runInEnv(Env.CLIENT) {{
ClientLifecycleEvent.CLIENT_SETUP.register {
Menus.registerScreens()
@@ -87,7 +81,7 @@ object NeoComputers {
Networking.channels.remove()
}
ClientLifecycleEvent.CLIENT_STARTED.register {
ClientLifecycleEvent.CLIENT_SETUP.register {
Networking.allNodes.remove()
Networking.wirelessNodes.remove()
Networking.channels.remove()
@@ -111,19 +105,22 @@ object NeoComputers {
packet, ctx ->
val player = ctx.player
if(player is ServerPlayer) {
NodeSynchronizer.screenMap[player]?.processScreenInteraction(player, packet.buffer)
val ent = NodeSynchronizer.screenMap[player]
if(ent is DeviceNode) {
ent.processScreenInteraction(player, packet.buffer)
}
}
})
}
// we have to do this because the datagen task runs in the physical server
EnvExecutor.runInEnv(Env.CLIENT) {{
NetworkManager.registerReceiver(NetworkManager.s2c(),NodeSynchronizer.StatePayload.TYPE, NodeSynchronizer.StatePayload.CODEC, {
NetworkManager.registerReceiver(NetworkManager.s2c(),NodeSynchronizer.DeviceBlockStatePayload.TYPE, NodeSynchronizer.DeviceBlockStatePayload.CODEC, {
packet, ctx ->
val level = ctx.player.level()
val ent = level.getBlockEntity(packet.blockPos)
if(ent is NodeBlockEntity) {
ent.syncWithUpstream(packet.buffer)
if(ent is DeviceBlockEntity) {
ent.processCommits(packet.buffers)
}
})
@@ -143,7 +140,7 @@ object NeoComputers {
}}
EnvExecutor.runInEnv(Env.SERVER) {{
// https://github.com/architectury/architectury-api/issues/518
NetworkManager.registerS2CPayloadType(NodeSynchronizer.StatePayload.TYPE, NodeSynchronizer.StatePayload.CODEC)
NetworkManager.registerS2CPayloadType(NodeSynchronizer.DeviceBlockStatePayload.TYPE, NodeSynchronizer.DeviceBlockStatePayload.CODEC)
NetworkManager.registerS2CPayloadType(NodeSynchronizer.ScreenPayload.TYPE, NodeSynchronizer.ScreenPayload.CODEC)
NetworkManager.registerS2CPayloadType(NodeSynchronizer.BeepDataPayload.TYPE, NodeSynchronizer.BeepDataPayload.CODEC)
}}

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 {

View File

@@ -88,12 +88,11 @@ object BlockEntities {
}
fun registerPowerBlocks() {
// TODO: function for the new DeviceBlockEntity
//PowerManager.registerPowerBlockEntity(CAPACITOR_ENTITY.get())
//PowerManager.registerPowerBlockEntity(CAPACITOR2_ENTITY.get())
//PowerManager.registerPowerBlockEntity(CAPACITOR3_ENTITY.get())
PowerManager.registerPowerBlockEntity(SOLARGEN_ENTITY.get())
PowerManager.registerPowerBlockEntity(COMBUSTGEN_ENTITY.get())
PowerManager.registerPowerBlockEntity(CASE_ENTITY.get())
PowerManager.registerPowerDevice(CAPACITOR_ENTITY.get())
PowerManager.registerPowerDevice(CAPACITOR2_ENTITY.get())
PowerManager.registerPowerDevice(CAPACITOR3_ENTITY.get())
PowerManager.registerPowerDevice(SOLARGEN_ENTITY.get())
PowerManager.registerPowerDevice(COMBUSTGEN_ENTITY.get())
PowerManager.registerPowerDevice(CASE_ENTITY.get())
}
}

View File

@@ -2,15 +2,31 @@ package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.world.item.DyeColor
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.CableBlock
import org.neoflock.neocomputers.block.CableBlock.Companion.COLOR
import org.neoflock.neocomputers.block.CableBlock.Companion.getPropByDirection
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.network.DeviceNode
class CableEntity(pos: BlockPos, state: BlockState) : BlockEntity(BlockEntities.CABLE_ENTITY.get(), pos, state) {
class CableEntity(pos: BlockPos, state: BlockState) : SingleDeviceBlockEntity(BlockEntities.CABLE_ENTITY.get(), pos, state) {
override val deviceNode = object : DeviceNode(){}
override fun sendCommitsToClient(level: Level) {
// we have nothing to commit lol
return
}
override fun getNodeFromSide(directionToRequester: Direction): DeviceNode? {
if(CableBlock.shouldConnect(blockPos, blockPos.relative(directionToRequester), level!!)) {
return deviceNode
}
return null
}
override fun setChanged() {
super.setChanged()

View File

@@ -20,8 +20,7 @@ import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.CaseBlock
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.block.NodeSynchronizer
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.gui.menu.CaseMenu
import org.neoflock.neocomputers.item.ComponentItem
import org.neoflock.neocomputers.network.DeviceNode
@@ -33,8 +32,10 @@ import org.neoflock.neocomputers.utils.GenericContainer
import java.time.Duration
import kotlin.math.max
import kotlin.math.min
import org.neoflock.neocomputers.network.NodeSynchronizer
import kotlin.text.ifEmpty
class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEntity(BlockEntities.CASE_ENTITY.get(), blockPos, blockState), MachineEntity, GenericContainer, MenuProvider {
class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): SingleDeviceBlockEntity(BlockEntities.CASE_ENTITY.get(), blockPos, blockState), MachineEntity, GenericContainer, MenuProvider {
val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(7, ItemStack.EMPTY)
var isOn = false
@@ -47,45 +48,63 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
override val deviceNode = object : DeviceNode() {
override var powerRole = PowerRole.CONSUMER
override var energyCapacity: Long = 500
}
override fun encodeDownstreamData(packet: FriendlyByteBuf) {
super.encodeDownstreamData(packet)
packet.writeBoolean(isOn)
packet.writeVarInt(diskActivityTime)
packet.writeVarInt(networkActivityTime)
packet.writeUtf(err ?: "")
}
override fun syncWithUpstream(packet: FriendlyByteBuf) {
super.syncWithUpstream(packet)
setRunning(packet.readBoolean())
diskActivityTime = packet.readVarInt()
networkActivityTime = packet.readVarInt()
err = packet.readUtf().ifEmpty { null }
}
override fun processScreenInteraction(player: ServerPlayer, packet: FriendlyByteBuf) {
val c = packet.readByte().toInt()
if(c == 0x01) {
start()
override fun writeFullStateCommit(buf: FriendlyByteBuf) {
super.writeFullStateCommit(buf)
buf.writeUUID(address)
buf.writeBoolean(isOn)
buf.writeVarInt(diskActivityTime)
buf.writeVarInt(networkActivityTime)
buf.writeUtf(err ?: "")
}
if(c == 0x02) {
stop()
}
}
override fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {
super.encodeScreenData(player, packet)
packet.writeBoolean(isOn)
packet.writeByteArray((err ?: "").encodeToByteArray())
packet.writeLong(deviceNode.energy)
packet.writeLong(deviceNode.energyCapacity)
packet.writeLong(getMachineMemoryUsed())
packet.writeLong(getMachineMemoryTotal())
packet.writeLong(getMachineComponentsUsed())
packet.writeLong(getMachineComponentsTotal())
packet.writeUtf(arch)
override fun processCommit(buf: FriendlyByteBuf) {
super.processCommit(buf)
Networking.changeNodeAddress(this, buf.readUUID())
setRunning(buf.readBoolean())
diskActivityTime = buf.readVarInt()
networkActivityTime = buf.readVarInt()
err = buf.readUtf().ifEmpty { null }
}
override fun processScreenInteraction(player: ServerPlayer, buf: FriendlyByteBuf) {
super.processScreenInteraction(player, buf)
val c = buf.readByte().toInt()
if(c == 0x01) {
start()
}
if(c == 0x02) {
stop()
}
}
override fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) {
super.encodeScreenData(player, buf)
buf.writeBoolean(isOn)
buf.writeByteArray((err ?: "").encodeToByteArray())
buf.writeLong(energy)
buf.writeLong(energyCapacity)
buf.writeLong(getMachineMemoryUsed())
buf.writeLong(getMachineMemoryTotal())
buf.writeLong(getMachineComponentsUsed())
buf.writeLong(getMachineComponentsTotal())
buf.writeUtf(arch)
}
override fun tick() {
super.tick()
if (isRunning()) {
if(diskActivityTime > 0) diskActivityTime--
if(networkActivityTime > 0) networkActivityTime--
if(getMachineArchitectures().isEmpty()) {
crash("@neocomputers.errors.ENOCPU")
} else if(getMachineComponentsUsed() > getMachineComponentsTotal()) {
crash("@neocomputers.errors.E2BIG")
} else if (!consumeEnergy(1)) {
crash("@neocomputers.errors.ENOENJ")
}
}
}
}
val redstoneIn = Array(Direction.entries.size) {0}
@@ -154,9 +173,9 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
override fun start(): Boolean {
if(isOn) return true
err = null
val archs = getMachineArchitectures()
val architectures = getMachineArchitectures()
// Beep patterns taken from https://github.com/MightyPirates/OpenComputers/blob/571482db88080d56329e8f8cf0db2a90825bf1d7/src/main/scala/li/cil/oc/server/machine/Machine.scala
if(archs.isEmpty()) {
if(architectures.isEmpty()) {
crash("@neocomputers.errors.ENOCPU")
beepAsync("-..")
return false
@@ -178,9 +197,9 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
beepAsync("-.")
return false
}
if(arch !in archs) {
if(arch !in architectures) {
// Just pick one! TODO: consult EEPROM first
arch = archs.first()
arch = architectures.first()
}
beepAsync(".")
setRunning(true)
@@ -194,10 +213,8 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
}
override fun crash(error: String): Boolean {
if(isOn) {
beepAsync("--")
sendMachineEvent(MachineCrashEvent(this, error))
}
beepAsync("--")
sendMachineEvent(MachineCrashEvent(this, error))
setRunning(false)
err = error
return true
@@ -271,21 +288,4 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
setRunning(false)
super.setRemoved()
}
override fun tickNode(level: Level) {
super.tickNode(level)
if(!level.isClientSide) {
if (isRunning()) {
if(diskActivityTime > 0) diskActivityTime--
if(networkActivityTime > 0) networkActivityTime--
if(getMachineArchitectures().isEmpty()) {
crash("@neocomputers.errors.ENOCPU")
} else if(getMachineComponentsUsed() > getMachineComponentsTotal()) {
crash("@neocomputers.errors.E2BIG")
} else if (!deviceNode.consumeEnergy(1)) {
crash("@neocomputers.errors.ENOENJ")
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
@@ -15,16 +16,15 @@ import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.CombustionGeneratorBlock
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.gui.menu.CombustionGeneratorMenu
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
import org.neoflock.neocomputers.utils.GenericContainer
import org.neoflock.neocomputers.utils.ContainerUtils
import kotlin.math.min
class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : NodeBlockEntity(BlockEntities.COMBUSTGEN_ENTITY.get(), blockPos, blockState), GenericContainer, MenuProvider {
class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : SingleDeviceBlockEntity(BlockEntities.COMBUSTGEN_ENTITY.get(), blockPos, blockState), GenericContainer, MenuProvider {
val energyPerTick: Long = 50
var burningTimeRemaining: Int = 0
@@ -32,6 +32,11 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
override val deviceNode = object : DeviceNode() {
override var powerRole = PowerRole.GENERATOR
override var energyCapacity: Long = 100000
override fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) {
buf.writeLong(energy)
buf.writeLong(energyCapacity)
}
}
val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(1, ItemStack.EMPTY)
@@ -46,8 +51,8 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
return !this.isRemoved
}
override fun tickNode(level: Level) {
super.tickNode(level)
override fun tickDevice(level: Level) {
super.tickDevice(level)
// TODO: give us a block state tag for active
// keep combusting and shi
@@ -79,11 +84,6 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
level?.setBlockAndUpdate(blockPos, blockState.setValue(CombustionGeneratorBlock.COMBUSTGEN_ACTIVE, burningTimeRemaining > 0))
}
override fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {
packet.writeLong(deviceNode.energy)
packet.writeLong(deviceNode.energyCapacity)
}
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider)
deviceNode.energy = min(deviceNode.energyCapacity, compoundTag.getLong("energy"))

View File

@@ -1,39 +1,67 @@
package org.neoflock.neocomputers.entity;
import net.minecraft.core.BlockPos
import net.minecraft.locale.Language
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.gui.buffer.BufferRenderer
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.utils.GPUChar
import org.neoflock.neocomputers.utils.TextBuffer
import kotlin.text.ifEmpty
class ScreenEntity(blockPos: BlockPos, blockState: BlockState) :
NodeBlockEntity(BlockEntities.SCREEN_ENTITY.get(), blockPos, blockState) {
SingleDeviceBlockEntity(BlockEntities.SCREEN_ENTITY.get(), blockPos, blockState) {
var lastError: String? = null
var isOn: Boolean = false
override val deviceNode = object : DeviceNode() {
override fun received(message: Networking.Message) {
super.received(message)
if(message is Networking.ComputerEvent) {
// return if not directly connected
if(message.sender !in this.connections) return
val mEnv = message.machineEvent
NeoComputers.LOGGER.info("Got message $mEnv!")
if(mEnv is MachinePowerEvent) {
if(mEnv.nowRunning) {
textBuf.set(0, 0, address.toString())
} else {
lastError = null
textBuf.fill(0, 0, textBuf.width, textBuf.height, GPUChar(' '))
textBuf.set(0, 0, address.toString())
}
isOn = mEnv.nowRunning
}
if(mEnv is MachineCrashEvent) {
lastError = mEnv.error
}
}
}
override fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) {
super.encodeScreenData(player, buf)
textBuf.encodeContents(buf)
}
override fun writeFullStateCommit(buf: FriendlyByteBuf) {
super.writeFullStateCommit(buf)
buf.writeUUID(address)
buf.writeBoolean(isOn)
buf.writeUtf(lastError ?: "")
textBuf.encodeContents(buf)
}
override fun processCommit(buf: FriendlyByteBuf) {
super.processCommit(buf)
if(Networking.changeNodeAddress(this, buf.readUUID())) createscreenstuffs()
isOn = buf.readBoolean()
lastError = buf.readUtf().ifEmpty { null }
textBuf.decodeContents(buf)
}
}
var bound = "screen/unbound"
@@ -41,23 +69,8 @@ class ScreenEntity(blockPos: BlockPos, blockState: BlockState) :
private var cleanrenderer: () -> Unit = { }; // TODO: THIS SUCKS, FIND A BETTER WAY
override fun encodeDownstreamData(packet: FriendlyByteBuf) {
super.encodeDownstreamData(packet)
textBuf.encodeContents(packet)
}
override fun syncWithUpstream(packet: FriendlyByteBuf) {
super.syncWithUpstream(packet)
textBuf.decodeContents(packet)
}
override fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {
super.encodeScreenData(player, packet)
textBuf.encodeContents(packet)
}
override fun tickNode(level: Level) {
super.tickNode(level)
override fun tickDevice(level: Level) {
super.tickDevice(level)
cleanrenderer()
createscreenstuffs()
}
@@ -71,9 +84,29 @@ class ScreenEntity(blockPos: BlockPos, blockState: BlockState) :
private fun createscreenstuffs() {
bound = "screen/"+deviceNode.address.toString().replace("-", "_")
if (level!!.isClientSide) {
var renderer = BufferRenderer(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, bound), textBuf)
renderer.drawBuffer()
cleanrenderer = { renderer.clean() }
if(lastError == null) {
if(!isOn) {
textBuf.fill(0, 0, textBuf.width, textBuf.height)
}
var renderer = BufferRenderer(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, bound), textBuf)
renderer.drawBuffer()
cleanrenderer = { renderer.clean() }
} else {
var trueError = lastError!!
if(trueError.startsWith("@")) {
val trans = trueError.substring(1)
val lang = Language.getInstance()
trueError = lang.getOrDefault("neocomputers.computer.errorNoMsg", "Error: ") + lang.getOrDefault(trans)
}
val throwAwayBuf = TextBuffer(50, 16)
val fg = 0xFFFFFF
val bg = 0x2B68A6
throwAwayBuf.fill(0, 0, throwAwayBuf.width, throwAwayBuf.height, GPUChar(' ', fg, bg))
throwAwayBuf.set((throwAwayBuf.width - trueError.length) / 2, throwAwayBuf.height/2, trueError, fg, bg)
var renderer = BufferRenderer(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, bound), throwAwayBuf)
renderer.drawBuffer()
cleanrenderer = { renderer.clean() }
}
}
}
}

View File

@@ -1,16 +1,16 @@
package org.neoflock.neocomputers.entity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.block.SingleDeviceBlockEntity
import org.neoflock.neocomputers.network.DeviceNode
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
class SolarGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : NodeBlockEntity(BlockEntities.SOLARGEN_ENTITY.get(), blockPos, blockState) {
class SolarGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : SingleDeviceBlockEntity(BlockEntities.SOLARGEN_ENTITY.get(), blockPos, blockState) {
val energyPerTick: Long = 50
override val deviceNode = object : DeviceNode() {
@@ -18,8 +18,8 @@ class SolarGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState) : No
override var energyCapacity: Long = 50000
}
override fun tickNode(level: Level) {
super.tickNode(level)
override fun tickDevice(level: Level) {
super.tickDevice(level)
val l = level
if(l.isDay) {
deviceNode.giveEnergy(energyPerTick)

View File

@@ -27,6 +27,7 @@ class ScreenEntityRenderer(val context: BlockEntityRendererProvider.Context?) :
.createCompositeState(false))
}
override fun render(entity: ScreenEntity, partialTick: Float, mat: PoseStack, bufferSource: MultiBufferSource, packedLight: Int, packedOverlay: Int) {
if(!entity.isOn && entity.lastError == null) return
val facing = entity.blockState.getValue(ScreenBlock.FACING_HORIZ)
val vert = entity.blockState.getValue(ScreenBlock.FACING_VERTI)-1

View File

@@ -26,15 +26,11 @@ class BufferRenderer(private var id: ResourceLocation, private var buffer: TextB
Minecraft.getInstance().textureManager.register(this.id, tex)
}
fun dump(path: String) {
image.writeToFile(File(path))
NeoComputers.LOGGER.info("DUMPED!!!")
fun toRGBA(color: Int): Int {
// Minecaft lies, its AGBR
return java.lang.Integer.reverseBytes((color.toLong() * 256 + 0xFF).toInt())
}
// fun toRGBA(color: Int): Int {
// return color.shl(8).or(0xFF)
// }
fun drawGlyph(x: Int, y: Int, c: Char, fg: Int) {
var glyph: ArrayList<Byte> = FontProvider.map[c]!!
@@ -42,7 +38,7 @@ class BufferRenderer(private var id: ResourceLocation, private var buffer: TextB
for (i in 0..<CHARW) {
// var pixel = ((glyph[j] and ((1 shl (CHARW - i - 1)).toByte())).toInt()) ushr (CHARW - i - 1) // retardation
var pixel = (glyph[j] and (0b10000000 ushr i).toByte()).toInt()
if (pixel > 0) image.setPixelRGBA(x+i, y+j, 0xFF000000.toInt()+fg)
if (pixel > 0) image.setPixelRGBA(x+i, y+j, toRGBA(fg))
}
}
}
@@ -53,7 +49,7 @@ class BufferRenderer(private var id: ResourceLocation, private var buffer: TextB
var char: GPUChar = buffer.get(i, j)
var x = i*CHARW
var y = j*CHARH
image.fillRect(x, y, CHARW, CHARH, (0xFF000000+char.bg).toInt())
image.fillRect(x, y, CHARW, CHARH, toRGBA(char.bg))
if (char.c != ' ' && char.c != '\u0000') drawGlyph(x, y, char.c, char.fg)
}
}

View File

@@ -11,7 +11,7 @@ import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.inventory.tooltip.TooltipComponent
import net.minecraft.world.phys.Vec3
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.NodeSynchronizer
import org.neoflock.neocomputers.network.NodeSynchronizer
import org.neoflock.neocomputers.gui.menu.CaseMenu
import org.neoflock.neocomputers.gui.widget.ButtonSprites
import org.neoflock.neocomputers.gui.widget.ImagerButton

View File

@@ -24,6 +24,8 @@ class ScreenScreen : GenericContainerScreen<ScreenMenu>{
var textBuf = TextBuffer(0, 0)
override fun shouldCenterTitle(): Boolean = false
override fun processScreenStatePacket(buf: FriendlyByteBuf) {
super.processScreenStatePacket(buf)
textBuf.decodeContents(buf)

View File

@@ -2,6 +2,7 @@ package org.neoflock.neocomputers.network
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.server.level.ServerPlayer
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.network.Networking.Message
import org.neoflock.neocomputers.network.Networking.Visibility
@@ -215,7 +216,8 @@ open class DeviceNode(_address: UUID? = null) {
outOfSync = true
}
open fun encodeScreenData(buf: FriendlyByteBuf) {}
open fun encodeScreenData(player: ServerPlayer, buf: FriendlyByteBuf) {}
open fun processScreenInteraction(player: ServerPlayer, buf: FriendlyByteBuf) {}
// Meant to write the entire state as a single commit
// for clients which say they have no fucking idea what the server is storing

View File

@@ -85,12 +85,14 @@ object Networking {
fun getNode(address: UUID): DeviceNode? = allNodes.get()[address]
// TODO: use setter, more convenient
fun changeNodeAddress(deviceNode: DeviceNode, address: UUID) {
if(deviceNode.address.equals(address)) return
if(deviceNode.address !in allNodes.get()) return
fun changeNodeAddress(deviceNode: DeviceNode, address: UUID): Boolean {
if(deviceNode.address.equals(address)) return false
if(deviceNode.address !in allNodes.get()) return false
if(address in allNodes.get()) return false
allNodes.get().remove(deviceNode.address)
deviceNode.address = address
allNodes.get()[address] = deviceNode
return true
}
fun addNode(deviceNode: DeviceNode) {
@@ -103,12 +105,17 @@ object Networking {
allNodes.get().forEach { it.value.onNodeAdded(deviceNode) }
}
fun addNodes(vararg deviceNodes: DeviceNode) {
fun addNodes(deviceNodes: Iterable<DeviceNode>) {
deviceNodes.forEach { addNode(it) }
}
fun addNodes(vararg deviceNodes: DeviceNode) {
addNodes(deviceNodes.asIterable())
}
fun removeNode(deviceNode: DeviceNode) {
if(deviceNode.address !in allNodes.get()) return
NodeSynchronizer.nodeErased(deviceNode)
allNodes.get().forEach { it.value.onNodeRemoved(deviceNode) }
// toList() in order to copy it
deviceNode.connections.toList().forEach {
@@ -121,10 +128,14 @@ object Networking {
}
}
fun removeNodes(vararg deviceNodes: DeviceNode) {
fun removeNodes(deviceNodes: Iterable<DeviceNode>) {
deviceNodes.forEach { removeNode(it) }
}
fun removeNodes(vararg deviceNodes: DeviceNode) {
removeNodes(deviceNodes.asIterable())
}
val channels = ThreadLocal.withInitial { HashMap<String, MutableSet<DeviceNode>>() }
fun addToChannel(channel: String, deviceNode: DeviceNode) {

View File

@@ -0,0 +1,150 @@
package org.neoflock.neocomputers.network
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.level.Level
import org.neoflock.neocomputers.NeoComputers
import java.time.Duration
object NodeSynchronizer {
class DeviceBlockStatePayload(var blockPos: BlockPos, var buffers: List<FriendlyByteBuf>): CustomPacketPayload {
companion object {
val NODE_SYNC_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "node_sync")
val TYPE = CustomPacketPayload.Type<DeviceBlockStatePayload>(NODE_SYNC_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, DeviceBlockStatePayload> {
override fun decode(buf: RegistryFriendlyByteBuf): DeviceBlockStatePayload {
val blockPos = buf.readBlockPos()
val bufferCount = buf.readVarInt()
val buffers = List(bufferCount) {
val bytes = buf.readByteArray()
val rawBuf = Unpooled.buffer(bytes.size)
rawBuf.writeBytes(bytes)
FriendlyByteBuf(rawBuf)
}
return DeviceBlockStatePayload(blockPos, buffers)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: DeviceBlockStatePayload) {
buf.writeBlockPos(payload.blockPos)
buf.writeVarInt(payload.buffers.size)
payload.buffers.forEach {
buf.writeByteArray(it.array())
}
}
}
}
override fun type() = TYPE
}
class ScreenPayload(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 buffer = FriendlyByteBuf(buf.copy(buf.readerIndex(), buf.readableBytes()))
return ScreenPayload(buffer)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: ScreenPayload) {
buf.writeBytes(payload.buffer)
}
}
}
override fun type() = TYPE
}
class ScreenDataPayload(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 buffer = FriendlyByteBuf(buf.copy(buf.readerIndex(), buf.readableBytes()))
return ScreenDataPayload(buffer)
}
override fun encode(buf: RegistryFriendlyByteBuf, payload: ScreenDataPayload) {
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, DeviceNode>()
fun playerScreenClosed(player: ServerPlayer) {
screenMap.remove(player)
}
fun registerPlayerScreen(player: ServerPlayer, devNode: DeviceNode) {
screenMap[player] = devNode
}
fun nodeErased(node: DeviceNode) {
var player: ServerPlayer? = null
for((p, n) in screenMap) {
if(n == node) player = p
}
if(player != null) screenMap.remove(player)
}
fun syncScreens() {
for((player, ent) in screenMap) {
val buf = FriendlyByteBuf(Unpooled.buffer())
ent.encodeScreenData(player, buf)
NetworkManager.sendToPlayer(player, ScreenPayload(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)
}
}
}
}

View File

@@ -1,9 +1,10 @@
package org.neoflock.neocomputers.network
import net.minecraft.world.level.block.entity.BlockEntityType
import org.neoflock.neocomputers.block.DeviceBlockEntity
//? if fabric {
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext
import org.neoflock.neocomputers.block.NodeBlockEntity
import net.minecraft.core.Direction
import team.reborn.energy.api.EnergyStorage;
//?}
@@ -11,31 +12,34 @@ import team.reborn.energy.api.EnergyStorage;
// the NodeBlockEntity and Node given us a way to get power from a block, we just
// need to tell mods how to do it as well
object PowerManager {
fun<T: NodeBlockEntity> registerPowerBlockEntity(blockEntityType: BlockEntityType<T>) {
fun<T: DeviceBlockEntity> registerPowerDevice(blockEntityType: BlockEntityType<T>) {
//? if fabric {
EnergyStorage.SIDED.registerForBlockEntity({
entity, dir -> object : EnergyStorage {
override fun getAmount() = entity.deviceNode.energy
override fun getCapacity() = entity.deviceNode.energyCapacity
override fun supportsExtraction() = entity.deviceNode.powerRole != PowerRole.CONSUMER && entity.deviceNode.energyCapacity > 0
override fun supportsInsertion() = entity.deviceNode.powerRole != PowerRole.GENERATOR
override fun extract(maxAmount: Long, transaction: TransactionContext?): Long {
if(entity.deviceNode.powerRole == PowerRole.CONSUMER) return 0
val taken = entity.deviceNode.withdrawEnergy(maxAmount)
transaction?.addCloseCallback {
ctx, res -> if(res.wasAborted() || !res.wasCommitted()) entity.deviceNode.giveEnergy(taken)
// TODO: as this is currently written, if the node instance changes and the mod cached the conversion, we're boned. Consider fixing it.
entity, dir ->
val node = entity.getNodeFromSide(dir ?: Direction.UP)
if(node == null) null else object : EnergyStorage {
override fun getAmount() = node.energy
override fun getCapacity() = node.energyCapacity
override fun supportsExtraction() = node.powerRole != PowerRole.CONSUMER && node.energyCapacity > 0
override fun supportsInsertion() = node.powerRole != PowerRole.GENERATOR
override fun extract(maxAmount: Long, transaction: TransactionContext?): Long {
if(node.powerRole == PowerRole.CONSUMER) return 0
val taken = node.withdrawEnergy(maxAmount)
transaction?.addCloseCallback {
ctx, res -> if(res.wasAborted() || !res.wasCommitted()) node.giveEnergy(taken)
}
return taken
}
return taken
}
override fun insert(maxAmount: Long, transaction: TransactionContext?): Long {
if(entity.deviceNode.powerRole == PowerRole.GENERATOR) return 0
val given = entity.deviceNode.giveEnergy(maxAmount)
transaction?.addCloseCallback { ctx, res ->
if (res.wasAborted() || !res.wasCommitted()) entity.deviceNode.withdrawEnergy(given)
override fun insert(maxAmount: Long, transaction: TransactionContext?): Long {
if(node.powerRole == PowerRole.GENERATOR) return 0
val given = node.giveEnergy(maxAmount)
transaction?.addCloseCallback { ctx, res ->
if (res.wasAborted() || !res.wasCommitted()) node.withdrawEnergy(given)
}
return given
}
return given
}
}
}, blockEntityType);
//?}
}