This commit is contained in:
2026-04-22 22:34:33 +02:00
17 changed files with 423 additions and 77 deletions

View File

@@ -3,9 +3,11 @@ package org.neoflock.neocomputers
import com.mojang.blaze3d.vertex.DefaultVertexFormat import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.VertexFormat import com.mojang.blaze3d.vertex.VertexFormat
import dev.architectury.event.events.client.ClientLifecycleEvent import dev.architectury.event.events.client.ClientLifecycleEvent
import dev.architectury.event.events.common.LifecycleEvent
import dev.architectury.event.events.common.PlayerEvent import dev.architectury.event.events.common.PlayerEvent
import dev.architectury.event.events.common.TickEvent import dev.architectury.event.events.common.TickEvent
import dev.architectury.networking.NetworkManager import dev.architectury.networking.NetworkManager
import dev.architectury.platform.Platform
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import org.neoflock.neocomputers.block.Blocks import org.neoflock.neocomputers.block.Blocks
import org.neoflock.neocomputers.entity.BlockEntities import org.neoflock.neocomputers.entity.BlockEntities
@@ -84,6 +86,22 @@ object NeoComputers {
NodeSynchronizer.syncScreens() NodeSynchronizer.syncScreens()
} }
TickEvent.PLAYER_POST.register {
Sounds.tickCustomSounds()
}
LifecycleEvent.SERVER_STARTING.register {
Networking.allNodes.remove()
Networking.wirelessNodes.remove()
Networking.channels.remove()
}
ClientLifecycleEvent.CLIENT_STARTED.register {
Networking.allNodes.remove()
Networking.wirelessNodes.remove()
Networking.channels.remove()
}
PlayerEvent.CLOSE_MENU.register { PlayerEvent.CLOSE_MENU.register {
player, menu -> player, menu ->
if(player is ServerPlayer) NodeSynchronizer.playerScreenClosed(player) if(player is ServerPlayer) NodeSynchronizer.playerScreenClosed(player)
@@ -94,6 +112,10 @@ object NeoComputers {
NodeSynchronizer.playerScreenClosed(player) NodeSynchronizer.playerScreenClosed(player)
} }
// networking has no way to define a C2S packet type, so we need the listener on both
// however, defining it separately on both breaks both ends
// so we define it once, but on both platforms
if(Platform.getEnvironment() == Env.CLIENT || Platform.getEnvironment() == Env.SERVER) {
NetworkManager.registerReceiver(NetworkManager.c2s(),NodeSynchronizer.ScreenDataPayload.TYPE, NodeSynchronizer.ScreenDataPayload.CODEC, { NetworkManager.registerReceiver(NetworkManager.c2s(),NodeSynchronizer.ScreenDataPayload.TYPE, NodeSynchronizer.ScreenDataPayload.CODEC, {
packet, ctx -> packet, ctx ->
val player = ctx.player val player = ctx.player
@@ -101,6 +123,7 @@ object NeoComputers {
NodeSynchronizer.screenMap[player]?.processScreenInteraction(player, packet.buffer) NodeSynchronizer.screenMap[player]?.processScreenInteraction(player, packet.buffer)
} }
}) })
}
// we have to do this because the datagen task runs in the physical server // we have to do this because the datagen task runs in the physical server
EnvExecutor.runInEnv(Env.CLIENT) {{ EnvExecutor.runInEnv(Env.CLIENT) {{
@@ -120,11 +143,18 @@ object NeoComputers {
scr.processScreenStatePacket(packet.buffer) scr.processScreenStatePacket(packet.buffer)
} }
}) })
NetworkManager.registerReceiver(NetworkManager.s2c(),NodeSynchronizer.BeepDataPayload.TYPE, NodeSynchronizer.BeepDataPayload.CODEC, {
packet, ctx ->
// TODO: implement volume
Sounds.beep(packet.pos.center, packet.pattern, packet.freq, packet.duration.toMillis().toInt())
})
}} }}
EnvExecutor.runInEnv(Env.SERVER) {{ EnvExecutor.runInEnv(Env.SERVER) {{
// https://github.com/architectury/architectury-api/issues/518 // https://github.com/architectury/architectury-api/issues/518
NetworkManager.registerS2CPayloadType(NodeSynchronizer.StatePayload.TYPE, NodeSynchronizer.StatePayload.CODEC) NetworkManager.registerS2CPayloadType(NodeSynchronizer.StatePayload.TYPE, NodeSynchronizer.StatePayload.CODEC)
NetworkManager.registerS2CPayloadType(NodeSynchronizer.ScreenPayload.TYPE, NodeSynchronizer.ScreenPayload.CODEC) NetworkManager.registerS2CPayloadType(NodeSynchronizer.ScreenPayload.TYPE, NodeSynchronizer.ScreenPayload.CODEC)
NetworkManager.registerS2CPayloadType(NodeSynchronizer.BeepDataPayload.TYPE, NodeSynchronizer.BeepDataPayload.CODEC)
}} }}
LOGGER.info("Registered!") LOGGER.info("Registered!")

View File

@@ -37,7 +37,7 @@ class CaseBlock() : NodeBlock(Properties.of().sound(SoundType.METAL).lightLevel(
} }
} }
override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState) = CaseBlockEntity(blockPos, blockState) override fun newBlockEntity(blockPos: BlockPos, blockState: BlockState) = CaseBlockEntity(blockPos, blockState).initNetworking()
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) { override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block?, BlockState?>) {
builder.add(COMPUTER_RUNNING) builder.add(COMPUTER_RUNNING)

View File

@@ -24,6 +24,8 @@ import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.network.Networking import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.network.PowerRole
import java.time.Duration
object NodeSynchronizer { object NodeSynchronizer {
class StatePayload(var blockPos: BlockPos, var buffer: FriendlyByteBuf): CustomPacketPayload { class StatePayload(var blockPos: BlockPos, var buffer: FriendlyByteBuf): CustomPacketPayload {
@@ -70,8 +72,8 @@ object NodeSynchronizer {
class ScreenDataPayload(var entityTypeWireID: String, var buffer: FriendlyByteBuf): CustomPacketPayload { class ScreenDataPayload(var entityTypeWireID: String, var buffer: FriendlyByteBuf): CustomPacketPayload {
companion object { companion object {
val SCREEN_SYNC_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen_data") val SCREEN_DATA_ID = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "screen_data")
val TYPE = CustomPacketPayload.Type<ScreenDataPayload>(SCREEN_SYNC_ID) val TYPE = CustomPacketPayload.Type<ScreenDataPayload>(SCREEN_DATA_ID)
val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, ScreenDataPayload> { val CODEC = object : StreamCodec<RegistryFriendlyByteBuf, ScreenDataPayload> {
override fun decode(buf: RegistryFriendlyByteBuf): ScreenDataPayload { override fun decode(buf: RegistryFriendlyByteBuf): ScreenDataPayload {
val id = buf.readByteArray().decodeToString() val id = buf.readByteArray().decodeToString()
@@ -89,6 +91,33 @@ object NodeSynchronizer {
override fun type() = TYPE 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 = mutableMapOf<ServerPlayer, NodeBlockEntity>() val screenMap = mutableMapOf<ServerPlayer, NodeBlockEntity>()
fun playerScreenClosed(player: ServerPlayer) { fun playerScreenClosed(player: ServerPlayer) {
@@ -112,6 +141,14 @@ object NodeSynchronizer {
fun sendScreenInteraction(friendlyByteBuf: FriendlyByteBuf) { fun sendScreenInteraction(friendlyByteBuf: FriendlyByteBuf) {
NetworkManager.sendToServer(ScreenDataPayload("", 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 class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : BlockEntity(blockEntityType, blockPos, blockState) {
@@ -134,7 +171,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
// runs on the client, meant to decode server state packets to synchronize client state // runs on the client, meant to decode server state packets to synchronize client state
open fun syncWithUpstream(packet: FriendlyByteBuf) { open fun syncWithUpstream(packet: FriendlyByteBuf) {
node.address = packet.readUUID() Networking.changeNodeAddress(node, packet.readUUID())
node.energy = packet.readLong() node.energy = packet.readLong()
node.energyCapacity = packet.readLong() node.energyCapacity = packet.readLong()
node.reachability = packet.readEnum(node.reachability.javaClass) node.reachability = packet.readEnum(node.reachability.javaClass)
@@ -176,7 +213,7 @@ abstract class NodeBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: Bl
stateIsDirty = true stateIsDirty = true
} }
fun needsSynchronization() = stateIsDirty open fun needsSynchronization() = stateIsDirty
open fun tickNode(level: Level) { open fun tickNode(level: Level) {
if(!level.isClientSide) { if(!level.isClientSide) {
@@ -225,7 +262,8 @@ abstract class NodeBlock(properties: Properties = Properties.of()): BaseBlock(pr
): BlockEntityTicker<T>? { ): BlockEntityTicker<T>? {
return object : BlockEntityTicker<T> { return object : BlockEntityTicker<T> {
override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) { override fun tick(level: Level, blockPos: BlockPos, blockState: BlockState, blockEntity: T) {
if(blockEntity !is NodeBlockEntity) return; if(blockEntity !is NodeBlockEntity) return
if(Networking.getNode(blockEntity.node.address) == null) blockEntity.initNetworking()
blockEntity.tickNode(level) blockEntity.tickNode(level)
} }
} }

View File

@@ -87,5 +87,6 @@ object BlockEntities {
PowerManager.registerPowerBlockEntity(CAPACITOR3_ENTITY.get()) PowerManager.registerPowerBlockEntity(CAPACITOR3_ENTITY.get())
PowerManager.registerPowerBlockEntity(SOLARGEN_ENTITY.get()) PowerManager.registerPowerBlockEntity(SOLARGEN_ENTITY.get())
PowerManager.registerPowerBlockEntity(COMBUSTGEN_ENTITY.get()) PowerManager.registerPowerBlockEntity(COMBUSTGEN_ENTITY.get())
PowerManager.registerPowerBlockEntity(CASE_ENTITY.get())
} }
} }

View File

@@ -1,10 +1,7 @@
package org.neoflock.neocomputers.entity package org.neoflock.neocomputers.entity
import net.minecraft.client.Minecraft 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.resources.sounds.SoundInstance
import net.minecraft.client.sounds.LoopingAudioStream
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.Direction import net.minecraft.core.Direction
import net.minecraft.core.HolderLookup import net.minecraft.core.HolderLookup
@@ -14,7 +11,6 @@ import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource import net.minecraft.sounds.SoundSource
import net.minecraft.world.Container
import net.minecraft.world.ContainerHelper import net.minecraft.world.ContainerHelper
import net.minecraft.world.MenuProvider import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
@@ -25,6 +21,7 @@ import net.minecraft.world.level.block.state.BlockState
import org.neoflock.neocomputers.NeoComputers import org.neoflock.neocomputers.NeoComputers
import org.neoflock.neocomputers.block.CaseBlock import org.neoflock.neocomputers.block.CaseBlock
import org.neoflock.neocomputers.block.NodeBlockEntity import org.neoflock.neocomputers.block.NodeBlockEntity
import org.neoflock.neocomputers.block.NodeSynchronizer
import org.neoflock.neocomputers.block.dirToIdx import org.neoflock.neocomputers.block.dirToIdx
import org.neoflock.neocomputers.gui.menu.CaseMenu import org.neoflock.neocomputers.gui.menu.CaseMenu
import org.neoflock.neocomputers.item.ComponentItem import org.neoflock.neocomputers.item.ComponentItem
@@ -40,10 +37,12 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(7, ItemStack.EMPTY) val stacks: NonNullList<ItemStack> = NonNullList<ItemStack>.withSize(7, ItemStack.EMPTY)
var isOn = false var isOn = false
var err: String? = null
var arch = "Lua 5.3"
var soundInstance: SoundInstance? = null var soundInstance: SoundInstance? = null
override val node = object : Networking.Node() { override val node = object : Networking.Node() {
override var powerRole = PowerRole.STORAGE override var powerRole = PowerRole.CONSUMER
override var energyCapacity: Long = 500 override var energyCapacity: Long = 500
} }
@@ -70,6 +69,14 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
override fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) { override fun encodeScreenData(player: ServerPlayer, packet: FriendlyByteBuf) {
super.encodeScreenData(player, packet) super.encodeScreenData(player, packet)
packet.writeBoolean(isOn) packet.writeBoolean(isOn)
packet.writeByteArray((err ?: "").encodeToByteArray())
packet.writeLong(node.energy)
packet.writeLong(node.energyCapacity)
packet.writeLong(getMachineMemoryUsed())
packet.writeLong(getMachineMemoryTotal())
packet.writeLong(getMachineComponentsUsed())
packet.writeLong(getMachineComponentsTotal())
packet.writeUtf(arch)
} }
val redstoneIn = Array(Direction.entries.size) {0} val redstoneIn = Array(Direction.entries.size) {0}
@@ -136,21 +143,56 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
} }
override fun start(): Boolean { override fun start(): Boolean {
if(isOn) return true
err = null
val archs = 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()) {
crash("no cpu")
beepAsync("-..")
return false
}
if(getMachineComponentsUsed() > getMachineComponentsTotal()) {
crash("too many components")
beepAsync("-..")
return false
}
if(node.energy < 100) {
crash("not enough energy")
// we add a beep for the special case where we do have a little bit of energy :P
if(node.energy > 0) beepAsync("..")
return false
}
if(getMachineMemoryTotal() == 0L) {
crash("no memory provided")
beepAsync("-.")
return false
}
if(arch !in archs) {
// Just pick one!
arch = archs.first()
}
beepAsync(".")
setRunning(true) setRunning(true)
return isOn return isOn
} }
override fun stop(): Boolean { override fun stop(): Boolean {
if(!isOn) return false
setRunning(false) setRunning(false)
return isOn return isOn
} }
override fun crash(error: String): Boolean { override fun crash(error: String): Boolean {
NeoComputers.LOGGER.warn("Crashing cases is not implemented yet lol") if(isOn) {
return false beepAsync("--")
}
setRunning(false)
err = error
return true
} }
override fun getLastError(): String? = null override fun getLastError(): String? = err
override fun getMachineNode(): Networking.Node = node override fun getMachineNode(): Networking.Node = node
@@ -165,19 +207,29 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
return old return old
} }
override fun beepAsync(frequency: Int, duration: Duration, volume: Double): Boolean { override fun beepAsync(pattern: String, frequency: Int, duration: Duration, volume: Double): Boolean {
NeoComputers.LOGGER.warn("beep not yet implemented") NodeSynchronizer.emitBeep(level!!, NodeSynchronizer.BeepDataPayload(getMachineBlockPosition(), pattern, frequency, duration, volume))
return true return true
} }
override fun getMachineMemoryTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getMemoryCapacity(it) }.sum().toLong() override fun getMachineMemoryTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getMemoryCapacity(it) }.sum().toLong()
override fun getMachineMemoryUsed(): Long = 0 override fun getMachineMemoryUsed(): Long = 0
override fun getMachineComponentsUsed(): Long = node.connections.size.toLong() override fun getMachineComponentsUsed(): Long = node.getReachable().size.toLong()
override fun getMachineComponentsTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getComponentCapacity(it) }.sum().toLong() override fun getMachineComponentsTotal(): Long = stacks.mapNotNull { (it.item as? ComponentItem)?.getComponentCapacity(it) }.sum().toLong()
override fun getMachineArchitecture() = arch
override fun getMachineArchitectures() = stacks.mapNotNull { (it.item as? ComponentItem)?.getArchitecturesProvided(it) }.flatten().toSet()
override fun setMachineArchitecture(arch: String) {
if(this.arch == arch) return
this.arch = arch
if(isRunning()) {
stop()
start()
}
}
override fun getItems(): NonNullList<ItemStack> = stacks override fun getItems(): NonNullList<ItemStack> = stacks
override fun stillValid(player: Player): Boolean = true override fun stillValid(player: Player): Boolean = !this.isRemoved
override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) { override fun loadAdditional(compoundTag: CompoundTag, provider: HolderLookup.Provider) {
super.loadAdditional(compoundTag, provider) super.loadAdditional(compoundTag, provider)
@@ -196,15 +248,19 @@ class CaseBlockEntity(blockPos: BlockPos, blockState: BlockState): NodeBlockEnti
override fun getDisplayName(): Component? = Component.literal("Computer") override fun getDisplayName(): Component? = Component.literal("Computer")
override fun createMenu(i: Int, inventory: Inventory, player: Player) = CaseMenu(i, inventory, this) 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() { override fun setRemoved() {
setRunning(false) setRunning(false)
super.setRemoved() super.setRemoved()
} }
override fun tickNode(level: Level) {
super.tickNode(level)
if(!level.isClientSide) {
if (isRunning()) {
if (!node.consumeEnergy(1)) {
crash("out of energy")
}
}
}
}
} }

View File

@@ -19,7 +19,12 @@ interface MachineEntity {
fun getMachineBlockPosition(): BlockPos fun getMachineBlockPosition(): BlockPos
fun getMachineLevel(): Level fun getMachineLevel(): Level
fun beepAsync(frequency: Int, duration: Duration, volume: Double): Boolean // Pattern can have dots (.), dashes (-) and spaces ( ).
// Each character is duration long, and has a 50ms break.
// For non-short ones, which are typically reserved only for hardware interactions,
// the duration is doubled.
// Architectures should only use short ones.
fun beepAsync(pattern: String, frequency: Int = 1000, duration: Duration = Duration.ofMillis(200), volume: Double = 1.0): Boolean
fun isRunning(): Boolean fun isRunning(): Boolean
fun start(): Boolean fun start(): Boolean
@@ -34,6 +39,9 @@ interface MachineEntity {
fun getMachineMemoryUsed(): Long fun getMachineMemoryUsed(): Long
fun getMachineComponentsUsed(): Long fun getMachineComponentsUsed(): Long
fun getMachineComponentsTotal(): Long fun getMachineComponentsTotal(): Long
fun getMachineArchitecture(): String
fun getMachineArchitectures(): Set<String>
fun setMachineArchitecture(arch: String)
// Redstone signals // Redstone signals
fun getRedstoneInput(direction: Direction): Int fun getRedstoneInput(direction: Direction): Int

View File

@@ -20,20 +20,20 @@ open class CaseMenu : GenericContainerMenu {
constructor(i: Int, inv: Inventory) : this(i, inv, SimpleContainer(7)) constructor(i: Int, inv: Inventory) : this(i, inv, SimpleContainer(7))
constructor(i: Int, inv: Inventory, container: Container) : super(Menus.CASE_MENU.get(), i, container) { constructor(i: Int, inv: Inventory, container: Container) : super(Menus.CASE_MENU.get(), i, container) {
this.addInventorySlots(inv, 8, 84)
val machine = container as? CaseBlockEntity val machine = container as? CaseBlockEntity
this.addSlot(ComponentSlot(this.container, 0, 20, 34, machine, eepromRequirement)) this.addSlot(ComponentSlot(container, 0, 20, 34, machine, eepromRequirement))
var i = 1 var i = 1
for ((col, slotCol) in slotRequirements.withIndex()) { for ((col, slotCol) in slotRequirements.withIndex()) {
for ((row, slotReq) in slotCol.withIndex()) { for ((row, slotReq) in slotCol.withIndex()) {
this.addSlot(ComponentSlot(this.container, i, 98+(col*22), 18*(row+1)-2, machine, slotReq)) this.addSlot(ComponentSlot(container, i, 98+(col*22), 18*(row+1)-2, machine, slotReq))
i++ i++
} }
} }
this.addInventorySlots(inv, 8, 84)
// for (int col=1; col<4; col++) { // for (int col=1; col<4; col++) {
// for (int row=1; row<4; row++) { // for (int row=1; row<4; row++) {
// int i = (row-1)*3+(col-1); // int i = (row-1)*3+(col-1);

View File

@@ -1,26 +1,24 @@
package org.neoflock.neocomputers.gui.screen; package org.neoflock.neocomputers.gui.screen;
import com.mojang.blaze3d.vertex.BufferBuilder
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexFormat
import io.netty.buffer.Unpooled import io.netty.buffer.Unpooled
import net.minecraft.ChatFormatting
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.ImageButton
import net.minecraft.client.gui.components.WidgetSprites
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.network.FriendlyByteBuf import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory 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.NeoComputers
import org.neoflock.neocomputers.block.NodeSynchronizer import org.neoflock.neocomputers.block.NodeSynchronizer
import org.neoflock.neocomputers.gui.menu.CaseMenu import org.neoflock.neocomputers.gui.menu.CaseMenu
import org.neoflock.neocomputers.gui.widget.ButtonSprites import org.neoflock.neocomputers.gui.widget.ButtonSprites
import org.neoflock.neocomputers.gui.widget.ImagerButton import org.neoflock.neocomputers.gui.widget.ImagerButton
import org.neoflock.neocomputers.sounds.Sounds
import org.neoflock.neocomputers.utils.Formatting
import org.neoflock.neocomputers.utils.GenericContainerScreen import org.neoflock.neocomputers.utils.GenericContainerScreen
import java.util.Optional
class CaseScreen : GenericContainerScreen<CaseMenu> { class CaseScreen : GenericContainerScreen<CaseMenu> {
private val PCB: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/computer.png") private val PCB: ResourceLocation = ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "textures/gui/computer.png")
@@ -34,11 +32,49 @@ class CaseScreen : GenericContainerScreen<CaseMenu> {
override fun shouldCenterTitle(): Boolean = false override fun shouldCenterTitle(): Boolean = false
var isOn = false var isOn = false
var lastError: String? = null
var energy: Long = 0L
var maxEnergy: Long = 0L
var memory: Long = 0L
var maxMemory: Long = 0L
var components: Long = 0L
var maxComponents: Long = 0L
var arch = ""
override fun processScreenStatePacket(buf: FriendlyByteBuf) { override fun processScreenStatePacket(buf: FriendlyByteBuf) {
super.processScreenStatePacket(buf) super.processScreenStatePacket(buf)
isOn = buf.readBoolean() isOn = buf.readBoolean()
btn?.pressed = isOn btn?.pressed = isOn
val error = buf.readByteArray().decodeToString()
if(error.isEmpty()) {
lastError = null
} else {
lastError = error
}
energy = buf.readLong()
maxEnergy = buf.readLong()
memory = buf.readLong()
maxMemory = buf.readLong()
components = buf.readLong()
maxComponents = buf.readLong()
arch = buf.readUtf()
}
fun computeButtonTooltip(): List<Component> {
val msgs = mutableListOf(Component.literal("Computer " + if(isOn) "ON" else "OFF").withStyle(if(isOn) ChatFormatting.GREEN else ChatFormatting.RED))
if(lastError != null) {
msgs.addLast(Component.literal("Error: ").withStyle(ChatFormatting.RED).append(Component.literal(lastError!!)))
}
if(arch.isNotEmpty()) {
msgs.addLast(Component.literal("Architecture: $arch"))
}
if(hasShiftDown()) {
msgs.addLast(Component.literal("Energy: $energy / $maxEnergy J").withStyle(if(energy < 100) ChatFormatting.RED else ChatFormatting.WHITE))
msgs.addLast(Component.literal("Memory: ${Formatting.formatMemory(memory)} / ${Formatting.formatMemory(maxMemory)}"))
msgs.addLast(Component.literal("Components: $components / $maxComponents").withStyle(if(components <= maxComponents) ChatFormatting.WHITE else ChatFormatting.RED))
}
return msgs
} }
constructor(abstractContainerMenu: CaseMenu, inventory: Inventory, component: Component) : super(abstractContainerMenu, inventory, component) { constructor(abstractContainerMenu: CaseMenu, inventory: Inventory, component: Component) : super(abstractContainerMenu, inventory, component) {
@@ -64,14 +100,20 @@ class CaseScreen : GenericContainerScreen<CaseMenu> {
guiGraphics.blit(PCB, relX, relY, 0, 0, this.imageWidth, this.imageHeight) guiGraphics.blit(PCB, relX, relY, 0, 0, this.imageWidth, this.imageHeight)
} }
override fun renderCustomOverlay(graphics: GuiGraphics, mouseX: Int, mouseY: Int, blend: Float) {
super.renderCustomOverlay(graphics, mouseX, mouseY, blend)
if(btn!!.isHovered) {
graphics.renderTooltip(this.font, computeButtonTooltip(), Optional.empty<TooltipComponent>(), mouseX, mouseY)
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { // todo: make a better widget system than mojang, practically not even using the fact it's a widget atp override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { // todo: make a better widget system than mojang, practically not even using the fact it's a widget atp
NeoComputers.LOGGER.info(String.format("btn: %d %d %d %d, mouse %s %s", btn!!.x, btn!!.y, btn!!.x+btn!!.width, btn!!.y+btn!!.height, mouseX.toString(), mouseY.toString())) if (button == 0 && btn!!.isHovered) {
if (button != 0) return false
if (btn!!.x < mouseX.toInt() && mouseX.toInt() < btn!!.x+btn!!.width && btn!!.y < mouseY.toInt() && mouseY.toInt() < btn!!.y+btn!!.height) {
btn!!.playDownSound(Minecraft.getInstance().soundManager) btn!!.playDownSound(Minecraft.getInstance().soundManager)
btn!!.onClick(mouseX, mouseY) btn!!.onClick(mouseX, mouseY)
return true return true
} else return false }
return super.mouseClicked(mouseX, mouseY, button)
} }
} }

View File

@@ -13,6 +13,8 @@ open class CPUItem(val tier: Int, val maxComponents: Int): Item(Item.Properties(
override fun getComponentCapacity(itemStack: ItemStack): Int = maxComponents override fun getComponentCapacity(itemStack: ItemStack): Int = maxComponents
override fun getArchitecturesProvided(itemStack: ItemStack): Set<String> = setOf("Lua 5.3")
override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity): Networking.Node? = null override fun toComponentNode(itemStack: ItemStack, machine: MachineEntity): Networking.Node? = null
} }

View File

@@ -14,6 +14,7 @@ interface ComponentItem {
// Get machine properties they can influence // Get machine properties they can influence
fun getMemoryCapacity(itemStack: ItemStack): Int = 0 fun getMemoryCapacity(itemStack: ItemStack): Int = 0
fun getComponentCapacity(itemStack: ItemStack): Int = 0 fun getComponentCapacity(itemStack: ItemStack): Int = 0
fun getArchitecturesProvided(itemStack: ItemStack): Set<String> = setOf()
// Component placed, node must now exist // Component placed, node must now exist
fun whenComponentPlaced(itemStack: ItemStack, machine: MachineEntity, newRole: String) { fun whenComponentPlaced(itemStack: ItemStack, machine: MachineEntity, newRole: String) {

View File

@@ -19,4 +19,11 @@ object DataComponents {
DataComponentType.builder<ByteBuffer>().persistent(Codec.BYTE_BUFFER).build()) DataComponentType.builder<ByteBuffer>().persistent(Codec.BYTE_BUFFER).build())
val EEPROM_DATA = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_data"), val EEPROM_DATA = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_data"),
DataComponentType.builder<ByteBuffer>().persistent(Codec.BYTE_BUFFER).build()) DataComponentType.builder<ByteBuffer>().persistent(Codec.BYTE_BUFFER).build())
val EEPROM_CODESIZE = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_codesize"),
DataComponentType.builder<Int>().persistent(Codec.INT).build())
val EEPROM_DATASIZE = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "eeprom_datasize"),
DataComponentType.builder<Int>().persistent(Codec.INT).build())
val TUNNEL_CHANNEL = Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, "tunnel_channel"),
DataComponentType.builder<String>().persistent(Codec.STRING).build())
} }

View File

@@ -13,6 +13,8 @@ import java.nio.ByteBuffer
fun getEEPROMProperties(codeCap: Int, dataCap: Int): Item.Properties = Item.Properties() fun getEEPROMProperties(codeCap: Int, dataCap: Int): Item.Properties = Item.Properties()
.component(DataComponents.EEPROM_CODE, ByteBuffer.allocate(codeCap)) .component(DataComponents.EEPROM_CODE, ByteBuffer.allocate(codeCap))
.component(DataComponents.EEPROM_DATA, ByteBuffer.allocate(dataCap)) .component(DataComponents.EEPROM_DATA, ByteBuffer.allocate(dataCap))
.component(DataComponents.EEPROM_CODESIZE, 0)
.component(DataComponents.EEPROM_DATASIZE, 0)
.component(DataComponents.LABEL, "") .component(DataComponents.LABEL, "")
.component(DataComponents.READONLY, false) .component(DataComponents.READONLY, false)
@@ -39,8 +41,8 @@ open class EEPROMItem(val tier: Int, val codeCapacity: Int, val dataCapacity: In
tooltipFlag: TooltipFlag tooltipFlag: TooltipFlag
) { ) {
if(tooltipFlag.isAdvanced) { if(tooltipFlag.isAdvanced) {
val codeSize = itemStack.get(DataComponents.EEPROM_CODE)?.position() ?: 0 val codeSize = itemStack.get(DataComponents.EEPROM_CODESIZE) ?: 0
val dataSize = itemStack.get(DataComponents.EEPROM_DATA)?.position() ?: 0 val dataSize = itemStack.get(DataComponents.EEPROM_DATASIZE) ?: 0
val addr = itemStack.get(DataComponents.ADDRESS) val addr = itemStack.get(DataComponents.ADDRESS)
val readonly = itemStack.get(DataComponents.READONLY) ?: false val readonly = itemStack.get(DataComponents.READONLY) ?: false
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr) val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)

View File

@@ -72,6 +72,7 @@ object Tabs {
codeBuf.put(code) codeBuf.put(code)
luaBios.set(DataComponents.LABEL, "Lua BIOS") luaBios.set(DataComponents.LABEL, "Lua BIOS")
luaBios.set(DataComponents.EEPROM_CODE, codeBuf) luaBios.set(DataComponents.EEPROM_CODE, codeBuf)
luaBios.set(DataComponents.EEPROM_CODESIZE, code.size)
output.accept(luaBios) output.accept(luaBios)
} while(false) } while(false)
} }

View File

@@ -7,9 +7,8 @@ import net.minecraft.world.item.TooltipFlag
import org.neoflock.neocomputers.entity.MachineEntity import org.neoflock.neocomputers.entity.MachineEntity
import org.neoflock.neocomputers.gui.widget.ComponentRoles import org.neoflock.neocomputers.gui.widget.ComponentRoles
import org.neoflock.neocomputers.network.Networking import org.neoflock.neocomputers.network.Networking
import org.neoflock.neocomputers.utils.Formatting
class TunnelCard: Item(Properties()), ComponentItem { class TunnelCard: Item(Properties().component(DataComponents.TUNNEL_CHANNEL, "creative")), ComponentItem {
// yes, we're counting TUNNEL as a conventional networking card // yes, we're counting TUNNEL as a conventional networking card
override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.CARD, ComponentRoles.NETWORK) override fun getComponentRoles(itemStack: ItemStack): Set<String> = setOf(ComponentRoles.CARD, ComponentRoles.NETWORK)
@@ -33,6 +32,7 @@ class TunnelCard: Item(Properties()), ComponentItem {
val addr = itemStack.get(DataComponents.ADDRESS) val addr = itemStack.get(DataComponents.ADDRESS)
val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr) val addrComp = if(addr == null) Component.translatable("neocomputers.noaddr") else Component.literal(addr)
list.addLast(addrComp) list.addLast(addrComp)
list.addLast(Component.translatable("neocomputers.tunnel.channel", itemStack.get(DataComponents.TUNNEL_CHANNEL) ?: "creative"))
// TODO: show max packet size and whatnot // TODO: show max packet size and whatnot
} }
super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag) super.appendHoverText(itemStack, tooltipContext, list, tooltipFlag)

View File

@@ -246,8 +246,8 @@ object Networking {
abstract fun receiveWireless(message: Message, emitter: WirelessEndpoint) abstract fun receiveWireless(message: Message, emitter: WirelessEndpoint)
} }
val wirelessNodes = mutableSetOf<WirelessEndpoint>() val wirelessNodes = ThreadLocal.withInitial { mutableSetOf<WirelessEndpoint>() }
val allNodes = mutableMapOf<UUID, Node>() val allNodes = ThreadLocal.withInitial { mutableMapOf<UUID, Node>() }
// node may differ from message.sender in the case of relays, // node may differ from message.sender in the case of relays,
// as they might have DIRECT reachability but // as they might have DIRECT reachability but
@@ -267,7 +267,7 @@ object Networking {
val startPos = starter.getPosition(); val startPos = starter.getPosition();
val startDim = starter.getDimension(); val startDim = starter.getDimension();
val range = starter.getRange(); val range = starter.getRange();
wirelessNodes.forEach { wirelessNodes.get().forEach {
if(it.getDimension() != startDim) return; if(it.getDimension() != startDim) return;
val pos = it.getPosition(); val pos = it.getPosition();
val d = distanceBetween(startPos, pos); val d = distanceBetween(startPos, pos);
@@ -279,27 +279,29 @@ object Networking {
} }
fun tickAllNodes() { fun tickAllNodes() {
allNodes.forEach { it.value.tick() } allNodes.get().forEach { it.value.tick() }
tickCount++ tickCount++
} }
fun getNode(address: UUID): Node? = allNodes[address] fun getNode(address: UUID): Node? = allNodes.get()[address]
// TODO: use setter, more convenient // TODO: use setter, more convenient
fun changeNodeAddress(node: Node, address: UUID) { fun changeNodeAddress(node: Node, address: UUID) {
allNodes.remove(node.address) if(node.address.equals(address)) return
if(node.address !in allNodes.get()) return
allNodes.get().remove(node.address)
node.address = address node.address = address
allNodes[address] = node allNodes.get()[address] = node
} }
fun addNode(node: Node) { fun addNode(node: Node) {
if(node.address in allNodes) return; if(node.address in allNodes.get()) return
allNodes[node.address] = node allNodes.get()[node.address] = node
if(node is WirelessEndpoint) { if(node is WirelessEndpoint) {
wirelessNodes.add(node); wirelessNodes.get().add(node);
} }
// notify at the end so it is notified of its own creation // notify at the end so it is notified of its own creation
allNodes.forEach { it.value.onNodeAdded(node) } allNodes.get().forEach { it.value.onNodeAdded(node) }
} }
fun addNodes(vararg nodes: Node) { fun addNodes(vararg nodes: Node) {
@@ -307,16 +309,16 @@ object Networking {
} }
fun removeNode(node: Node) { fun removeNode(node: Node) {
if(node.address !in allNodes) return if(node.address !in allNodes.get()) return
allNodes.forEach { it.value.onNodeRemoved(node) } allNodes.get().forEach { it.value.onNodeRemoved(node) }
// toList() in order to copy it // toList() in order to copy it
node.connections.toList().forEach { node.connections.toList().forEach {
node.disconnectFrom(it) node.disconnectFrom(it)
} }
// actually remove at the end so it can listen to its own removal // actually remove at the end so it can listen to its own removal
allNodes.remove(node.address) allNodes.get().remove(node.address)
if(node is WirelessEndpoint) { if(node is WirelessEndpoint) {
wirelessNodes.remove(node); wirelessNodes.get().remove(node);
} }
} }
@@ -324,25 +326,28 @@ object Networking {
nodes.forEach { removeNode(it) } nodes.forEach { removeNode(it) }
} }
val channels = mutableMapOf<String, MutableSet<Node>>(); val channels = ThreadLocal.withInitial { mutableMapOf<String, MutableSet<Node>>() }
fun addToChannel(channel: String, node: Node) { fun addToChannel(channel: String, node: Node) {
if(!channels.containsKey(channel)) { val localChannels = channels.get()
channels[channel] = mutableSetOf(); if(!localChannels.containsKey(channel)) {
localChannels[channel] = mutableSetOf();
} }
channels[channel]!!.add(node); localChannels[channel]!!.add(node);
} }
fun removeFromChannel(channel: String, node: Node) { fun removeFromChannel(channel: String, node: Node) {
if(!channels.containsKey(channel)) return; val localChannels = channels.get()
channels[channel]?.remove(node); if(!localChannels.containsKey(channel)) return;
if(channels[channel].isNullOrEmpty()) { localChannels[channel]?.remove(node);
channels.remove(channel); if(localChannels[channel].isNullOrEmpty()) {
localChannels.remove(channel);
} }
} }
fun emitChannelMessage(starter: Node, channel: String, message: Message) { fun emitChannelMessage(starter: Node, channel: String, message: Message) {
val c = channels[channel] ?: return; val localChannels = channels.get()
val c = localChannels[channel] ?: return;
c.forEach { if(it != starter) it.received(message); }; c.forEach { if(it != starter) it.received(message); };
} }
} }

View File

@@ -1,10 +1,20 @@
package org.neoflock.neocomputers.sounds package org.neoflock.neocomputers.sounds
import dev.architectury.registry.registries.DeferredRegister import dev.architectury.registry.registries.DeferredRegister
import net.minecraft.client.Minecraft
import net.minecraft.core.registries.Registries import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent import net.minecraft.sounds.SoundEvent
import net.minecraft.world.phys.Vec3
import org.lwjgl.BufferUtils
import org.neoflock.neocomputers.NeoComputers import org.neoflock.neocomputers.NeoComputers
import org.lwjgl.openal.AL10
import java.nio.ByteBuffer
import kotlin.experimental.xor
import kotlin.math.PI
import kotlin.math.max
import kotlin.math.sign
import kotlin.math.sin
object Sounds { object Sounds {
val SOUNDS = DeferredRegister.create(NeoComputers.MODID, Registries.SOUND_EVENT)!! val SOUNDS = DeferredRegister.create(NeoComputers.MODID, Registries.SOUND_EVENT)!!
@@ -14,4 +24,145 @@ object Sounds {
fun registerSound(name: String) = SOUNDS.register(name) { fun registerSound(name: String) = SOUNDS.register(name) {
SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, name)) SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(NeoComputers.MODID, name))
}!! }!!
val BEEP_SAMPLERATE = 44100
val BEEP_AMPLITUDE = 32f
val BEEP_MAXDIST = 16f
// Also largely taken from https://github.com/MightyPirates/OpenComputers/blob/571482db88080d56329e8f8cf0db2a90825bf1d7/src/main/scala/li/cil/oc/util/Audio.scala
val allSounds = ThreadLocal.withInitial { mutableListOf<CustomSoundBuffer>() }
class CustomSoundBuffer {
var dead: Boolean = true
var buffer: Int = -1
var source: Int = -1
fun start(x: Float, y: Float, z: Float, data: ByteBuffer, gain: Float): Int? {
// clear errors or smth idk
AL10.alGetError()
// written in a C style by a C dev
// all this work on a JVM project and I'm still writing C
// would be better if Kotlin had goto btw just saying
val ok = AL10.AL_NO_ERROR
var err = ok
buffer = AL10.alGenBuffers()
err = AL10.alGetError()
if(err != ok) return err
AL10.alBufferData(buffer, AL10.AL_FORMAT_MONO8, data, BEEP_SAMPLERATE)
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
return err
}
source = AL10.alGenSources()
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
return err
}
AL10.alSourceQueueBuffers(source, buffer)
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
AL10.alDeleteSources(source)
return err
}
AL10.alSource3f(source, AL10.AL_POSITION, x, y, z)
AL10.alSourcef(source, AL10.AL_REFERENCE_DISTANCE, BEEP_MAXDIST)
AL10.alSourcef(source, AL10.AL_MAX_DISTANCE, BEEP_MAXDIST)
AL10.alSourcef(source, AL10.AL_GAIN, gain * 0.3f)
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
AL10.alDeleteSources(source)
return err
}
AL10.alSourcePlay(source)
err = AL10.alGetError()
if(err != ok) {
AL10.alDeleteBuffers(buffer)
AL10.alDeleteSources(source)
return err
}
dead = false
return null
}
fun checkDone(): Boolean {
if(dead) return true
if(AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING) return false
NeoComputers.LOGGER.info("sound buffer stopped")
dead = true
AL10.alDeleteSources(source)
AL10.alDeleteBuffers(buffer)
return true
}
}
fun beep(pos: Vec3, pattern: String, frequency: Int = 1000, duration: Int = 200) {
NeoComputers.LOGGER.info("Beep: $pattern, $frequency Hz, $duration ms")
val mc = Minecraft.getInstance()
val playerPos = mc.player?.position() ?: pos
val distanceBasedGain = max(0.0, 1 - pos.distanceTo(playerPos) / BEEP_MAXDIST).toFloat()
val volume = 1.0
val gain = distanceBasedGain * volume
if (gain <= 0 || BEEP_AMPLITUDE <= 0) return
// Algorithm effectively ported over from https://github.com/MightyPirates/OpenComputers/blob/571482db88080d56329e8f8cf0db2a90825bf1d7/src/main/scala/li/cil/oc/util/Audio.scala
// We do add support for spaces tho
val charArr = pattern.toCharArray()
val sampleCounts = charArr.map { if(it == '.') duration else 2 * duration }.map { it * BEEP_SAMPLERATE / 1000 }
val pauseSample = 50 * BEEP_SAMPLERATE / 1000
val finalBuf = BufferUtils.createByteBuffer(sampleCounts.sum() + pauseSample * sampleCounts.lastIndex)
val step = frequency.toFloat() / BEEP_SAMPLERATE
var off = 0f
for((i, sampleCount) in sampleCounts.withIndex()) {
if(charArr[i] == ' ') {
for(sample in 0..<sampleCount) {
finalBuf.put(127)
}
} else {
for(sample in 0..<sampleCount) {
val angle = 2 * PI * off
val value = (sin(angle).sign * BEEP_AMPLITUDE).toInt().toByte().xor(0x80.toByte())
off += step
if(off > 1) off -= 1f
finalBuf.put(value)
}
}
if(finalBuf.hasRemaining()) {
for(sample in 0..<pauseSample) {
finalBuf.put(127)
}
}
}
finalBuf.rewind()
val l = mutableListOf<Int>()
while(finalBuf.hasRemaining()) l.addLast(finalBuf.get().toInt())
finalBuf.rewind()
NeoComputers.LOGGER.info("$l")
val sound = CustomSoundBuffer()
val soundErr = sound.start(pos.x.toFloat(), pos.y.toFloat(), pos.z.toFloat(), finalBuf, gain.toFloat())
if(soundErr != null) {
NeoComputers.LOGGER.error("Playing beep failed, OpenAL exit code of $soundErr")
return
}
NeoComputers.LOGGER.info("Beeping with ${finalBuf.capacity()} samples")
allSounds.get().addLast(sound)
}
fun tickCustomSounds() {
allSounds.get().removeIf { it.checkDone() }
}
} }

View File

@@ -28,6 +28,7 @@
"item.neocomputers.lan": "Wired Network Card", "item.neocomputers.lan": "Wired Network Card",
"item.neocomputers.wlan0": "Wireless Network Card (Tier 1)", "item.neocomputers.wlan0": "Wireless Network Card (Tier 1)",
"item.neocomputers.wlan1": "Wireless Network Card (Tier 2)", "item.neocomputers.wlan1": "Wireless Network Card (Tier 2)",
"item.neocomputers.tunnel": "Linked Card",
"item.neocomputers.data0": "Data Card (Tier 1)", "item.neocomputers.data0": "Data Card (Tier 1)",
"item.neocomputers.data1": "Data Card (Tier 2)", "item.neocomputers.data1": "Data Card (Tier 2)",
"item.neocomputers.data2": "Data Card (Tier 3)", "item.neocomputers.data2": "Data Card (Tier 3)",
@@ -47,5 +48,6 @@
"neocomputers.memory.capacity": "Capacity: %1$s", "neocomputers.memory.capacity": "Capacity: %1$s",
"neocomputers.eeprom.codeused": "Code Storage: %1$s / %2$s", "neocomputers.eeprom.codeused": "Code Storage: %1$s / %2$s",
"neocomputers.eeprom.dataused": "Data Storage: %1$s / %2$s", "neocomputers.eeprom.dataused": "Data Storage: %1$s / %2$s",
"neocomputers.tunnel.channel": "Linked Channel: %1$s",
"sounds.neocomputers.computer_running": "Computer Fans" "sounds.neocomputers.computer_running": "Computer Fans"
} }