innovation

This commit is contained in:
2026-04-21 20:51:30 +02:00
parent f2f79244e6
commit 7542052d1e
24 changed files with 564 additions and 104 deletions

View File

@@ -19,6 +19,7 @@ import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.item.Items
import org.neoflock.neocomputers.item.Tabs
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.sounds.Sounds
import org.neoflock.neocomputers.utils.FontProvider
import org.neoflock.neocomputers.utils.GenericContainerScreen
import org.slf4j.Logger
@@ -40,6 +41,7 @@ object NeoComputers {
BlockEntities.registerPowerBlocks()
Menus.MENUS.register()
Tabs.TABS.register()
Sounds.SOUNDS.register()
ComponentRoles.mapDefaultTextures()
// i dont know why architectury wants two lambdas but whatever
EnvExecutor.runInEnv(Env.CLIENT) {{
@@ -92,8 +94,14 @@ 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.ScreenPayload.TYPE, NodeSynchronizer.ScreenPayload.CODEC)
NetworkManager.registerReceiver(NetworkManager.c2s(),NodeSynchronizer.ScreenPayload.TYPE, NodeSynchronizer.ScreenPayload.CODEC, {
packet, ctx ->
val player = ctx.player
if(player is ServerPlayer) {
NodeSynchronizer.screenMap[player]?.processScreenInteraction(player, packet.buffer)
}
})
}}
LOGGER.info("Registered!")

View File

@@ -2,28 +2,89 @@ package org.neoflock.neocomputers.block;
import dev.architectury.registry.menu.MenuRegistry
import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component
import net.minecraft.core.Direction
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionHand
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
import net.minecraft.world.Containers
import net.minecraft.world.InteractionResult
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
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.entity.BlockEntity
import net.minecraft.world.level.block.SoundType
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.phys.BlockHitResult
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.CombustionGeneratorBlock.Companion.COMBUSTGEN_ACTIVE
import org.neoflock.neocomputers.entity.BlockEntities
import org.neoflock.neocomputers.entity.ScreenEntity
import org.neoflock.neocomputers.gui.menu.Menus
import org.neoflock.neocomputers.gui.menu.ScreenMenu
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.entity.CaseBlockEntity
import org.neoflock.neocomputers.entity.MachineEntity
import org.neoflock.neocomputers.sounds.Sounds
class CaseBlock() : NodeBlock(Properties.of().sound(SoundType.METAL).lightLevel(CaseBlock::getLuminance)) { // placeholder stuff
companion object {
val COMPUTER_RUNNING = BooleanProperty.create("running")!!
fun getLuminance(blockState: BlockState): Int {
return if(blockState.getValue(COMPUTER_RUNNING)) 3 else 0
}
}
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState) = CaseBlockEntity(blockPos, blockState)
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(COMPUTER_RUNNING)
}
fun getMachine(level: BlockGetter, blockPos: BlockPos): CaseBlockEntity {
return level.getBlockEntity(blockPos) as CaseBlockEntity
}
override fun getSignal(
blockState: BlockState,
blockGetter: BlockGetter,
blockPos: BlockPos,
direction: Direction
): Int {
return getMachine(blockGetter, blockPos).redstoneOut[dirToIdx(direction.opposite)]
}
override fun onPlace(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
blockState2: BlockState,
bl: Boolean
) {
if(!level.isClientSide) {
level.updateNeighborsAt(blockPos, this)
getMachine(level, blockPos).refetchAllRedstone()
}
super.onPlace(blockState, level, blockPos, blockState2, bl)
}
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 dir = Direction.getNearest(blockPos2.center.subtract(blockPos.center))
getMachine(level, blockPos).refetchRedstone(dir)
}
}
class CaseBlock() : BaseBlock() { // placeholder stuff
override fun useWithoutItem(
blockState: BlockState,
level: Level,
@@ -32,13 +93,21 @@ class CaseBlock() : BaseBlock() { // placeholder stuff
blockHitResult: BlockHitResult
): InteractionResult {
if(!level.isClientSide) {
MenuRegistry.openMenu(player as ServerPlayer, object : MenuProvider {
override fun getDisplayName(): Component = Component.literal("Computer")
override fun createMenu(i: Int, inventory: Inventory, player: Player): AbstractContainerMenu {
return Menus.CASE_MENU.get().create(i, inventory);
}
})
val ent = level.getBlockEntity(blockPos, BlockEntities.CASE_ENTITY.get()).get()
MenuRegistry.openMenu(player as ServerPlayer, ent)
NodeSynchronizer.registerPlayerScreen(player, ent)
}
return InteractionResult.SUCCESS
}
override fun onRemove(
blockState: BlockState,
level: Level,
blockPos: BlockPos,
blockState2: BlockState,
bl: Boolean
) {
Containers.dropContentsOnDestroy(blockState, blockState2, level, blockPos)
super.onRemove(blockState, level, blockPos, blockState2, bl)
}
}

View File

@@ -13,6 +13,7 @@ import net.minecraft.world.entity.player.Player
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
@@ -30,15 +31,15 @@ class SolarGeneratorBlock : NodeBlock(), EntityBlock {
// TODO: make it glow when burning
class CombustionGeneratorBlock : NodeBlock, EntityBlock {
companion object {
val ACTIVE = BooleanProperty.create("active")
val COMBUSTGEN_ACTIVE = BooleanProperty.create("active")
fun getLuminance(blockState: BlockState): Int {
return if(blockState.getValue(ACTIVE)) 5 else 0
return if(blockState.getValue(COMBUSTGEN_ACTIVE)) 5 else 0
}
}
constructor(): super(Properties.of().sound(SoundType.STONE).lightLevel(CombustionGeneratorBlock::getLuminance)) {
registerDefaultState(defaultBlockState().setValue(ACTIVE, false))
registerDefaultState(defaultBlockState().setValue(COMBUSTGEN_ACTIVE, false))
}
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState): BlockEntity = CombustionGeneratorBlockEntity(blockPos, blockState).initNetworking()
@@ -60,12 +61,12 @@ class CombustionGeneratorBlock : NodeBlock, EntityBlock {
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(ACTIVE)
builder.add(COMBUSTGEN_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)
if(blockState.getValue(COMBUSTGEN_ACTIVE)) {
if(randomSource.nextDouble() < 0.1) level.playLocalSound(blockPos, SoundEvents.FURNACE_FIRE_CRACKLE, SoundSource.AMBIENT, 1F, 1F, false)
val x = blockPos.x.toDouble()
val y = blockPos.y.toDouble()

View File

@@ -87,6 +87,10 @@ object NodeSynchronizer {
NetworkManager.sendToPlayer(player, ScreenPayload(nodeTypeToWireID(ent.type), buf))
}
}
fun sendScreenInteraction(friendlyByteBuf: FriendlyByteBuf) {
NetworkManager.sendToServer(ScreenPayload("", friendlyByteBuf))
}
}
abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : BlockEntity(blockEntityType, blockPos, blockState) {
@@ -119,6 +123,8 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
// 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> {

View File

@@ -75,6 +75,11 @@ object BlockEntities {
::CombustionGeneratorBlockEntity, mutableSetOf(Blocks.REDSTONEIO_BLOCK.get()), BullshitFix()
)
}
val CASE_ENTITY: RegistrySupplier<BlockEntityType<CaseBlockEntity>> = BLOCKENTITIES.register("case") {
BlockEntityType(
::CaseBlockEntity, mutableSetOf(Blocks.CASE_BLOCK.get()), BullshitFix()
)
}
fun registerPowerBlocks() {
PowerManager.registerPowerBlockEntity(CAPACITOR_ENTITY.get())

View File

@@ -0,0 +1,217 @@
package org.neoflock.neocomputers.entity
import net.minecraft.client.Minecraft
import net.minecraft.client.resources.sounds.BiomeAmbientSoundsHandler
import net.minecraft.client.resources.sounds.EntityBoundSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.client.sounds.LoopingAudioStream
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
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.world.Container
import net.minecraft.world.ContainerHelper
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
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.dirToIdx
import org.neoflock.neocomputers.gui.menu.CaseMenu
import org.neoflock.neocomputers.item.ComponentItem
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
import org.neoflock.neocomputers.sounds.ComputerRunningSoundInstance
import org.neoflock.neocomputers.sounds.Sounds
import org.neoflock.neocomputers.utils.GenericContainer
import java.time.Duration
import kotlin.math.min
class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEntity(BlockEntities.CASE_ENTITY.get(), blockPos, blockState), MachineEntity, GenericContainer, MenuProvider {
val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(7, ItemStack.EMPTY)
var isOn = false
var soundInstance: SoundInstance? = null
override val node = object : Networking.Node() {
override var powerRole = PowerRole.STORAGE
override var energyCapacity: Long = 500
}
override fun encodeDownstreamData(packet: FriendlyByteBuf) {
super.encodeDownstreamData(packet)
packet.writeBoolean(isOn)
}
override fun syncWithUpstream(packet: FriendlyByteBuf) {
super.syncWithUpstream(packet)
setRunning(packet.readBoolean())
}
override fun processScreenInteraction(player: ServerPlayer, packet: FriendlyByteBuf) {
val c = packet.readByte().toInt()
if(c == 0x01) {
start()
}
if(c == 0x02) {
stop()
}
}
override fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {
packet.writeBoolean(isOn)
packet.writeLong(node.energy)
packet.writeLong(node.energyCapacity)
packet.writeLong(getMachineMemoryUsed())
packet.writeLong(getMachineMemoryTotal())
packet.writeLong(getMachineComponentsUsed())
packet.writeLong(getMachineMemoryTotal())
}
val redstoneIn = Array(Direction.entries.size) {0}
val redstoneOut = Array(Direction.entries.size) {0}
fun refetchRedstone(dir: Direction) {
val src = blockPos.offset(dir.stepX, dir.stepY, dir.stepZ)
val cur = level?.getSignal(src, dir) ?: 0
val idx = dirToIdx(dir)
if(redstoneIn[idx] != cur) {
onRedstoneSignalChanged(dir, redstoneIn[idx], cur)
}
redstoneIn[idx] = cur
}
fun refetchAllRedstone() {
Direction.entries.forEach { refetchRedstone(it) }
}
fun sendMachineEvent(event: MachineEvent) {
stacks.forEach {
val item = it.item
if(item is ComponentItem) {
item.onMachineEvent(it, this, event)
}
}
}
fun onRedstoneSignalChanged(dir: Direction, oldValue: Int, newValue: Int) {
sendMachineEvent(MachineRedstoneEvent(this, dir, oldValue, newValue))
Networking.emitMessage(node, Networking.ComputerUncheckedSignal(node, "redstone_changed", arrayOf(node.address.toString(), dirToIdx(dir), oldValue, newValue)))
NeoComputers.LOGGER.info("redstone in direction ${dir.name} changed from $oldValue to $newValue")
if(oldValue == 0) {
// Rising edge
start()
}
if(newValue == 0) {
// Not accurate but whatevs
stop()
}
setChanged()
}
override fun getBlockPosition(): BlockPos = blockPos
override fun isRunning(): Boolean = isOn
fun setRunning(value: Boolean) {
if(isOn == value) return
NeoComputers.LOGGER.info("[${node.address}] Going from $isOn to $value")
isOn = value
val world = level ?: return
blockState?.setValue(CaseBlock.COMPUTER_RUNNING, isOn)
if(world.isClientSide) {
if(value) {
soundInstance = ComputerRunningSoundInstance(this, Sounds.COMPUTER_RUNNING.get(), SoundSource.AMBIENT)
Minecraft.getInstance().soundManager.play(soundInstance!!)
} else {
Minecraft.getInstance().soundManager.stop(soundInstance!!)
soundInstance = null
}
return
}
// Server-side stuff!!
world.onBlockStateChange(blockPos, blockState, blockState)
sendMachineEvent(MachinePowerEvent(this, isOn))
}
override fun start(): Boolean {
setRunning(true)
return isOn
}
override fun stop(): Boolean {
setRunning(false)
return isOn
}
override fun crash(error: String): Boolean {
NeoComputers.LOGGER.warn("Crashing cases is not implemented yet lol")
return false
}
override fun getLastError(): String? = null
override fun getMachineNode(): Networking.Node = node
override fun getRedstoneInput(direction: Direction): Int = redstoneIn[dirToIdx(direction)]
override fun getRedstoneOutput(direction: Direction): Int = redstoneOut[dirToIdx(direction)]
override fun setRedstoneOutput(direction: Direction, newValue: Int): Int {
val idx = dirToIdx(direction)
val old = redstoneOut[idx]
redstoneOut[idx] = newValue
return old
}
override fun beepAsync(frequency: Int, duration: Duration, volume: Double): Boolean {
NeoComputers.LOGGER.warn("beep not yet implemented")
return true
}
override fun getMachineMemoryTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getMemoryCapacity(it) }.sum().toLong()
override fun getMachineMemoryUsed(): Long = 0
override fun getMachineComponentsUsed(): Long = node.connections.size.toLong()
override fun getMachineComponentsTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getComponentCapacity(it) }.sum().toLong()
override fun getItems(): NonNullList<ItemStack> = stacks
override fun stillValid(player: Player): Boolean = true
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider)
node.energy = min(node.energyCapacity, compoundTag.getLong("energy"))
//isOn = compoundTag.getBoolean("powerOn")
ContainerHelper.loadAllItems(compoundTag, getItems(), provider)
}
override fun saveAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.saveAdditional(compoundTag, provider)
compoundTag.putLong("energy", node.energy)
//compoundTag.putBoolean("powerOn", isOn)
ContainerHelper.saveAllItems(compoundTag, getItems(), provider)
}
override fun getDisplayName(): Component? = Component.literal("Computer")
override fun createMenu(i: Int, inventory: Inventory, player: Player) = CaseMenu(i, inventory, this)
override fun canPlaceItem(i: Int, itemStack: ItemStack): Boolean = false
override fun canTakeItem(container: Container, i: Int, itemStack: ItemStack): Boolean = false
override fun setChanged() {
super.setChanged()
}
override fun setRemoved() {
setRunning(false)
super.setRemoved()
}
}

View File

@@ -5,20 +5,14 @@ import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.ContainerHelper
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.ChestBlock
import net.minecraft.world.level.block.FurnaceBlock
import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.block.CombustionGeneratorBlock
import org.neoflock.neocomputers.block.NodeBlockEntity
@@ -81,7 +75,7 @@ class CombustionGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
override fun setChanged() {
super.setChanged()
level?.setBlockAndUpdate(blockPos, blockState.setValue(CombustionGeneratorBlock.ACTIVE, burningTimeRemaining > 0))
level?.setBlockAndUpdate(blockPos, blockState.setValue(CombustionGeneratorBlock.COMBUSTGEN_ACTIVE, burningTimeRemaining > 0))
}
override fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {

View File

@@ -4,24 +4,35 @@ import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import org.neoflock.neocomputers.item.ComponentItem
import org.neoflock.neocomputers.network.Networking
import java.time.Duration
abstract class MachineEvent {
abstract val machine: MachineEntity
}
data class MachineRedstoneEvent(override val machine: MachineEntity, val side: Direction): MachineEvent()
data class MachineRedstoneEvent(override val machine: MachineEntity, val side: Direction, val oldValue: Int, val newValue: Int): MachineEvent()
data class MachinePowerEvent(override val machine: MachineEntity, val nowRunning: Boolean): MachineEvent()
interface MachineEntity {
// Block position of machine, for wireless tech
fun getBlockPosition(): BlockPos
fun beepAsync(frequency: Int, duration: Duration, volume: Double): Boolean
fun isRunning(): Boolean
fun start(): Boolean
fun stop(): Boolean
fun crash(error: String): Boolean
fun getLastError(): String?
fun getMachineNode(): Networking.Node
// Some metadata
fun getMachineMemoryTotal(): Long
fun getMachineMemoryUsed(): Long
fun getMachineComponentsUsed(): Long
fun getMachineComponentsTotal(): Long
// Redstone signals
fun getRedstoneInput(direction: Direction): Int
fun getRedstoneOutput(direction: Direction): Int

View File

@@ -1,49 +1,15 @@
package org.neoflock.neocomputers.gui.menu;
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import org.neoflock.neocomputers.gui.menu.Menus;
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.entity.MachineEntity
import org.neoflock.neocomputers.entity.CaseBlockEntity
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.gui.widget.ComponentSlot
import org.neoflock.neocomputers.gui.widget.ComponentSlotRequirement
import org.neoflock.neocomputers.gui.widget.DynamicSlot
import org.neoflock.neocomputers.item.ComponentItem
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.utils.GenericContainerMenu
// lowest IQ machine to exist
class DumbMachine: MachineEntity {
override fun getBlockPosition() = BlockPos.ZERO!!
override fun isRunning() = false
override fun start() = false
override fun stop() = false
override fun crash(error: String) = false
override fun getMachineNode() = Networking.Node()
override fun getRedstoneInput(direction: Direction): Int = 0
override fun getRedstoneOutput(direction: Direction): Int = 0
override fun setRedstoneOutput(direction: Direction, newValue: Int): Int = 0
}
class CaseMenu : GenericContainerMenu {
constructor(i: Int, inv: Inventory) : this(i, inv, SimpleContainer(7))
open class CaseMenu : GenericContainerMenu {
open val eepromRequirement = ComponentSlotRequirement(1, ComponentRoles.FIRMWARE)
open val slotRequirements = listOf(
listOf(ComponentSlotRequirement(1, ComponentRoles.CARD), ComponentSlotRequirement(1, ComponentRoles.CARD)),
@@ -51,18 +17,23 @@ class CaseMenu : GenericContainerMenu {
listOf(ComponentSlotRequirement(1, ComponentRoles.STORAGE)),
)
constructor(i: Int, inv: Inventory) : this(i, inv, SimpleContainer(7))
constructor(i: Int, inv: Inventory, container: Container) : super(Menus.CASE_MENU.get(), i, container) {
this.addInventorySlots(inv, 8, 84)
this.addSlot(ComponentSlot(this.container!!, 0, 20, 34, DumbMachine(), eepromRequirement))
val machine = container as? CaseBlockEntity
this.addSlot(ComponentSlot(this.container, 0, 20, 34, machine, eepromRequirement))
var i = 1
for ((col, slotCol) in slotRequirements.withIndex()) {
for ((row, slotReq) in slotCol.withIndex()) {
this.addSlot(ComponentSlot(this.container!!, i, 98+(col*22), 18*(row+1)-2, DumbMachine(), slotReq))
this.addSlot(ComponentSlot(this.container, i, 98+(col*22), 18*(row+1)-2, machine, slotReq))
i++
}
}
// for (int col=1; col<4; col++) {
// for (int row=1; row<4; row++) {
// int i = (row-1)*3+(col-1);

View File

@@ -1,14 +1,13 @@
package org.neoflock.neocomputers.gui.menu;
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import org.neoflock.neocomputers.gui.menu.Menus;
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.utils.GenericContainerMenu
class ScreenMenu(i: Int, inv: Inventory) : AbstractContainerMenu(Menus.SCREEN_MENU.get(), i) {
override fun stillValid(player: Player) = true // TODO: implement this properly
override fun quickMoveStack(player: Player, i: Int): ItemStack = ItemStack.EMPTY // there's no container here anyways
class ScreenMenu(i: Int, inv: Inventory) : GenericContainerMenu(Menus.SCREEN_MENU.get(), i, SimpleContainer(0)) {
}

View File

@@ -34,7 +34,7 @@ object ProgressBar { // TODO: variable length
guiGraphics.blit(BAR, x+1, y, width-2, height, 1F, 0F, 1, 14, 3, 14)
guiGraphics.blit(BAR, x+width-1, y, 1, height, 2F, 0F, 1, 14, 3, 14)
val frac = value.toFloat() / max.toFloat()
val frac = if(max == 0L) 0.0f else value.toFloat() / max.toFloat()
val linew = ceil(frac*(width-2).toFloat())
guiGraphics.fill(
RenderType.gui(),

View File

@@ -7,6 +7,7 @@ import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.ImageButton
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory
@@ -18,9 +19,29 @@ import org.neoflock.neocomputers.utils.GenericContainerScreen
class CaseScreen : GenericContainerScreen<CaseMenu> {
private val PCB: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/computer.png")
private val POWER_ATLAS: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/button_power.png")
private val BTN_SIZE = 18
override fun shouldCenterTitle(): Boolean = false
var isOn = false
var energy = 0L
var energyTotal = 0L
var memUsed = 0L
var memTotal = 0L
var compUsed = 0L
var compTotal = 0L
override fun processScreenStatePacket(buf: FriendlyByteBuf) {
isOn = buf.readBoolean()
energy = buf.readLong()
energyTotal = buf.readLong()
memUsed = buf.readLong()
memTotal = buf.readLong()
compUsed = buf.readLong()
compTotal = buf.readLong()
}
constructor(abstractContainerMenu: CaseMenu, inventory: Inventory, component: Component) : super(abstractContainerMenu, inventory, component)
override fun renderBg(guiGraphics: GuiGraphics, f: Float, i: Int, j: Int) {
super.renderBg(guiGraphics, f, i ,j)
@@ -33,6 +54,14 @@ class CaseScreen : GenericContainerScreen<CaseMenu> {
}
override fun renderCustomOverlay(graphics: GuiGraphics, mouseX: Int, mouseY: Int, blend: Float) {
super.renderCustomOverlay(graphics, mouseX, mouseY, blend)
val relX = (this.width - this.imageWidth) / 2
val relY = (this.height - this.imageHeight) / 2
graphics.blit(POWER_ATLAS, relX, relY, BTN_SIZE, BTN_SIZE, 0.5f, 0.5f, BTN_SIZE, BTN_SIZE, BTN_SIZE*2, BTN_SIZE*2)
}
// private fun renderSlots(relX: Int, relY: Int) { // TODO: put this in some generic screen class
// for (slot in menu.slots) {
// if (slot is DynamicSlot) {

View File

@@ -85,7 +85,7 @@ data class ComponentSlotRequirement(val tier: Int, val role: String) {
// Tier 0 allows ALL tiers, making it completely untiered.
// Role determines what the role is.
class ComponentSlot(container: Container, slot: Int, x: Int, y: Int, val machine: MachineEntity, val requirement: ComponentSlotRequirement): DynamicSlot(container, slot, x, y) {
class ComponentSlot(container: Container, slot: Int, x: Int, y: Int, val machine: MachineEntity?, val requirement: ComponentSlotRequirement): DynamicSlot(container, slot, x, y) {
override fun draw(graphics: GuiGraphics, relX: Int, relY: Int, mouseX: Int, mouseY: Int) {
super.draw(graphics, relX, relY, mouseX, mouseY)
if(!hasItem()) {
@@ -115,16 +115,20 @@ class ComponentSlot(container: Container, slot: Int, x: Int, y: Int, val machine
override fun set(itemStack: ItemStack) {
super.set(itemStack)
val item = itemStack.item
if(item is ComponentItem) {
item.whenComponentPlaced(itemStack, machine, requirement.role)
if(machine != null) {
val item = itemStack.item
if (item is ComponentItem) {
item.whenComponentPlaced(itemStack, machine, requirement.role)
}
}
}
override fun onTake(player: Player, itemStack: ItemStack) {
val item = itemStack.item
if(item is ComponentItem) {
item.whenComponentTaken(itemStack, machine, requirement.role)
if(machine != null) {
val item = itemStack.item
if (item is ComponentItem) {
item.whenComponentTaken(itemStack, machine, requirement.role)
}
}
super.onTake(player, itemStack)
}

View File

@@ -21,11 +21,13 @@ interface ComponentItem {
if(oldNode != null) Networking.removeNode(oldNode) // did a mod forget to call whenComponentTaken?
val node = toComponentNode(itemStack, machine) ?: return
Networking.addNode(node)
machine.getMachineNode().connectTo(node)
}
// Component taken, and thus removed
fun whenComponentTaken(itemStack: ItemStack, machine: MachineEntity, previousRole: String) {
val node = getComponentNode(itemStack) ?: return
node.disconnectFrom(machine.getMachineNode())
Networking.removeNode(node)
}

View File

@@ -6,6 +6,7 @@ import net.minecraft.core.component.DataComponentType
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.NeoComputers
import java.nio.ByteBuffer
object DataComponents {
val ADDRESS = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "address"),
@@ -15,7 +16,7 @@ object DataComponents {
val READONLY = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "readonly"),
DataComponentType.builder<Boolean>().persistent(Codec.BOOL).build())
val EEPROM_CODE = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_code"),
DataComponentType.builder<String>().persistent(Codec.STRING).build())
DataComponentType.builder<ByteBuffer>().persistent(Codec.BYTE_BUFFER).build())
val EEPROM_DATA = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_data"),
DataComponentType.builder<String>().persistent(Codec.STRING).build())
DataComponentType.builder<ByteBuffer>().persistent(Codec.BYTE_BUFFER).build())
}

View File

@@ -8,15 +8,15 @@ import org.neoflock.neocomputers.entity.MachineEntity
import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.utils.Formatting
import java.util.UUID
import java.nio.ByteBuffer
fun getEEPROMProperties(): Item.Properties = Item.Properties()
.component(DataComponents.EEPROM_CODE, "")
.component(DataComponents.EEPROM_DATA, "")
fun getEEPROMProperties(codeCap: Int, dataCap: Int): Item.Properties = Item.Properties()
.component(DataComponents.EEPROM_CODE, ByteBuffer.allocate(codeCap))
.component(DataComponents.EEPROM_DATA, ByteBuffer.allocate(dataCap))
.component(DataComponents.LABEL, "")
.component(DataComponents.READONLY, false)
open class EEPROMItem(val tier: Int, val codeCapacity: Int, val dataCapacity: Int): Item(getEEPROMProperties()), ComponentItem {
open class EEPROMItem(val tier: Int, val codeCapacity: Int, val dataCapacity: Int): Item(getEEPROMProperties(codeCapacity, dataCapacity)), ComponentItem {
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.FIRMWARE)
override fun getComponentTier(itemStack: ItemStack): Int = tier
@@ -39,12 +39,10 @@ open class EEPROMItem(val tier: Int, val codeCapacity: Int, val dataCapacity: In
tooltipFlag: TooltipFlag
) {
if(tooltipFlag.isAdvanced) {
val code = itemStack.get(DataComponents.EEPROM_CODE) ?: ""
val data = itemStack.get(DataComponents.EEPROM_DATA) ?: ""
val codeSize = itemStack.get(DataComponents.EEPROM_CODE)?.position() ?: 0
val dataSize = itemStack.get(DataComponents.EEPROM_DATA)?.position() ?: 0
val addr = itemStack.get(DataComponents.ADDRESS)
val readonly = itemStack.get(DataComponents.READONLY) ?: false
val codeSize = code.encodeToByteArray().size
val dataSize = data.encodeToByteArray().size
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
list.addLast(addrComp)
list.addLast(Component.translatable("neocomputers.eeprom.codeused", Formatting.formatMemory(codeSize.toLong()),

View File

@@ -20,7 +20,7 @@ open class GPUCard(val tier: Int, val vram: Long): Item(Properties()), Component
super.whenComponentPlaced(itemStack, machine, newRole)
}
// TODO: Modem Component
// TODO: GPU Component
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity): Networking.Node? = null
override fun appendHoverText(

View File

@@ -2,11 +2,14 @@ package org.neoflock.neocomputers.item
import dev.architectury.registry.CreativeTabRegistry
import dev.architectury.registry.registries.DeferredRegister
import net.minecraft.client.Minecraft
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.CreativeModeTab
import net.minecraft.world.item.ItemStack
import org.neoflock.neocomputers.NeoComputers
import java.nio.ByteBuffer
object Tabs {
val TABS: DeferredRegister<CreativeModeTab> =
@@ -53,11 +56,24 @@ object Tabs {
output.accept(ItemStack(Items.EE0.get()))
val luaBios = ItemStack(Items.EE0.get())
luaBios.set(DataComponents.LABEL, "Lua BIOS")
luaBios.set(DataComponents.EEPROM_CODE, "error('hi')")
luaBios.set(DataComponents.EEPROM_DATA, "random garbage")
output.accept(luaBios)
// Criminal black magic to put LuaBIOS EEPROM in the tabs
do {
val luaBios = ItemStack(Items.EE0.get())
val res = Minecraft.getInstance().resourceManager.getResourceOrThrow(
ResourceLocation.fromNamespaceAndPath(
NeoComputers.MODID,
"lua/oc_bios.lua"
)
)
val stream = res.openAsReader()
val code = stream.readText().encodeToByteArray()
stream.close()
val codeBuf = ByteBuffer.allocate(code.size)
codeBuf.put(code)
luaBios.set(DataComponents.LABEL, "Lua BIOS")
luaBios.set(DataComponents.EEPROM_CODE, codeBuf)
output.accept(luaBios)
} while(false)
}
}
}

View File

@@ -0,0 +1,35 @@
package org.neoflock.neocomputers.sounds
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import org.neoflock.neocomputers.entity.MachineEntity
class ComputerRunningSoundInstance: AbstractTickableSoundInstance {
val machine: MachineEntity
fun updatePosition() {
val pos = machine.getBlockPosition()
this.x = pos.x.toDouble() + 0.5
this.y = pos.y.toDouble() + 0.5
this.z = pos.z.toDouble() + 0.5
}
constructor(machine: MachineEntity, soundEvent: SoundEvent, soundSource: SoundSource): super(soundEvent, soundSource, SoundInstance.createUnseededRandom()) {
this.machine = machine
this.looping = true
this.delay = 0
this.volume = 1.0F
this.relative = true
updatePosition()
}
override fun tick() {
if(!machine.isRunning()) {
this.stop()
} else {
updatePosition()
}
}
}

View File

@@ -0,0 +1,17 @@
package org.neoflock.neocomputers.sounds
import dev.architectury.registry.registries.DeferredRegister
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import org.neoflock.neocomputers.NeoComputers
object Sounds {
val SOUNDS = DeferredRegister.create(NeoComputers.MODID, Registries.SOUND_EVENT)!!
val COMPUTER_RUNNING = registerSound("computer_running")
fun registerSound(name: String) = SOUNDS.register(name) {
SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, name))
}!!
}

View File

@@ -62,7 +62,7 @@ interface GenericContainer : Container {
}
}
abstract class GenericContainerMenu(menuType: MenuType<*>, id: Int, var container: Container?): AbstractContainerMenu(menuType, id) {
abstract class GenericContainerMenu(menuType: MenuType<*>, id: Int, var container: Container): AbstractContainerMenu(menuType, id) {
fun addInventorySlots(inventory: Inventory, x: Int, y: Int) {
// Based off the code in ChestMenu
for (i in 0..2) {
@@ -88,7 +88,7 @@ abstract class GenericContainerMenu(menuType: MenuType<*>, id: Int, var containe
val stack = slot.item
val copied = stack.copy()
val contSize = container!!.containerSize
val contSize = container.containerSize
if(i < contSize) {
if(!this.moveItemStackTo(stack, contSize, slots.size, true)) {
@@ -108,7 +108,7 @@ abstract class GenericContainerMenu(menuType: MenuType<*>, id: Int, var containe
}
override fun stillValid(player: Player): Boolean {
return container!!.stillValid(player)
return container.stillValid(player)
}
}
@@ -148,9 +148,13 @@ abstract class GenericContainerScreen<T: GenericContainerMenu>(menu: T, inventor
}
}
open fun renderCustomOverlay(graphics: GuiGraphics, mouseX: Int, mouseY: Int, blend: Float) {
}
override fun render(graphics: GuiGraphics, mouseX: Int, mouseY: Int, something: Float) {
super.render(graphics, mouseX, mouseY, something)
renderCustomOverlay(graphics, mouseX, mouseY, something)
if(shouldRenderTooltip()) super.renderTooltip(graphics, mouseX, mouseY)
}
}

View File

@@ -0,0 +1,64 @@
local init
do
local component_invoke = component.invoke
local function boot_invoke(address, method, ...)
local result = table.pack(pcall(component_invoke, address, method, ...))
if not result[1] then
return nil, result[2]
else
return table.unpack(result, 2, result.n)
end
end
-- backwards compatibility, may remove later
local eeprom = component.list("eeprom")()
computer.getBootAddress = function()
return boot_invoke(eeprom, "getData")
end
computer.setBootAddress = function(address)
return boot_invoke(eeprom, "setData", address)
end
do
local screen = component.list("screen")()
local gpu = component.list("gpu")()
if gpu and screen then
boot_invoke(gpu, "bind", screen)
end
end
local function tryLoadFrom(address)
local handle, reason = boot_invoke(address, "open", "/init.lua")
if not handle then
return nil, reason
end
local buffer = ""
repeat
local data, reason = boot_invoke(address, "read", handle, math.maxinteger or math.huge)
if not data and reason then
return nil, reason
end
buffer = buffer .. (data or "")
until not data
boot_invoke(address, "close", handle)
return load(buffer, "=init")
end
local reason
if computer.getBootAddress() then
init, reason = tryLoadFrom(computer.getBootAddress())
end
if not init then
computer.setBootAddress()
for address in component.list("filesystem") do
init, reason = tryLoadFrom(address)
if init then
computer.setBootAddress(address)
break
end
end
end
if not init then
error("no bootable medium found" .. (reason and (": " .. tostring(reason)) or ""), 0)
end
computer.beep(1000, 0.2)
end
return init()

View File

@@ -0,0 +1,9 @@
{
"computer_running": {
"subtitle": "sounds.neocomputers.computer_running",
"category": "ambient",
"sounds": [
{"name": "neocomputers:computer_running", "stream": true}
]
}
}