mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 11:34:05 +09:00
still wip modularisation, game somehow boots
This commit is contained in:
@@ -0,0 +1,585 @@
|
||||
package net.torvald.terrarum.modulecomputers.virtualcomputer.computer
|
||||
|
||||
import org.luaj.vm2.Globals
|
||||
import org.luaj.vm2.LuaError
|
||||
import org.luaj.vm2.LuaTable
|
||||
import org.luaj.vm2.LuaValue
|
||||
import org.luaj.vm2.lib.TwoArgFunction
|
||||
import org.luaj.vm2.lib.ZeroArgFunction
|
||||
import org.luaj.vm2.lib.jse.JsePlatform
|
||||
import net.torvald.terrarum.KVHashMap
|
||||
import net.torvald.terrarum.Terrarum
|
||||
import net.torvald.terrarum.Second
|
||||
import net.torvald.terrarum.ceilInt
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VDUtil
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.VirtualDisk
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.worldobject.ComputerPartsCodex
|
||||
import org.lwjgl.BufferUtils
|
||||
import org.lwjgl.openal.AL10
|
||||
import java.io.*
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.logging.Level
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* A part that makes "computer fixture" actually work
|
||||
*
|
||||
* @param avFixtureComputer : actor values for FixtureComputerBase
|
||||
*
|
||||
* @param term : terminal that is connected to the computer fixtures, null if not connected any.
|
||||
* Created by minjaesong on 2016-09-10.
|
||||
*/
|
||||
class TerrarumComputer(peripheralSlots: Int) {
|
||||
|
||||
val DEBUG_UNLIMITED_MEM = false
|
||||
val DEBUG = true
|
||||
|
||||
|
||||
val maxPeripherals: Int = if (DEBUG) 32 else peripheralSlots
|
||||
|
||||
|
||||
lateinit var luaJ_globals: Globals
|
||||
private set
|
||||
|
||||
var stdout: PrintStream? = null
|
||||
private set
|
||||
var stderr: PrintStream? = null
|
||||
private set
|
||||
var stdin: InputStream? = null
|
||||
private set
|
||||
|
||||
val processorCycle: Int // number of Lua statement to process per tick (1/100 s)
|
||||
get() = ComputerPartsCodex.getProcessorCycles(computerValue.getAsInt("processor") ?: 0)
|
||||
val memSize: Int // in bytes; max: 8 GB
|
||||
get() {
|
||||
if (DEBUG_UNLIMITED_MEM) return 16.shl(20)// 16 MB
|
||||
|
||||
var size = 0
|
||||
for (i in 0..3)
|
||||
size += ComputerPartsCodex.getRamSize(computerValue.getAsInt("memSlot$i")!!)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
val UUID = java.util.UUID.randomUUID().toString()
|
||||
|
||||
val computerValue = KVHashMap()
|
||||
|
||||
var isHalted = false
|
||||
|
||||
lateinit var term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype
|
||||
private set
|
||||
|
||||
val peripheralTable = Array<net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral?>(peripheralSlots, { null }) // index == slot number
|
||||
|
||||
var stdinInput: Int = -1
|
||||
private set
|
||||
|
||||
|
||||
// os-related functions. These are called "machine" library-wise.
|
||||
private val startupTimestamp: Long = System.currentTimeMillis()
|
||||
/** Time elapsed since the power is on. */
|
||||
val milliTime: Int
|
||||
get() = (System.currentTimeMillis() - startupTimestamp).toInt()
|
||||
|
||||
/** String:
|
||||
* if it's UUID, formatted UUID as string, always 36 chars
|
||||
* if not (test purpose only!), just String
|
||||
*/
|
||||
val diskRack = HashMap<String, VirtualDisk>()
|
||||
|
||||
fun attachDisk(slot: String, filename: String) {
|
||||
computerValue[slot] = filename
|
||||
|
||||
// put disk in diskRack
|
||||
if (filename.isNotEmpty() && filename.isNotBlank()) {
|
||||
diskRack[slot] = VDUtil.readDiskArchive(
|
||||
File(Terrarum.currentSaveDir.path + "/computers/$filename").absoluteFile,
|
||||
Level.WARNING,
|
||||
{ },
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Filesystem.sysCharset
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
computerValue["memslot0"] = 4864 // -1 indicates mem slot is empty
|
||||
computerValue["memslot1"] = -1 // put index of item here
|
||||
computerValue["memslot2"] = -1 // ditto.
|
||||
computerValue["memslot3"] = -1 // do.
|
||||
|
||||
computerValue["processor"] = -1 // do.
|
||||
|
||||
// as in "dev/hda"; refers hard disk drive (and no partitioning)
|
||||
attachDisk("hda", "uuid_testhda")
|
||||
attachDisk("hdb", "")
|
||||
attachDisk("hdc", "")
|
||||
attachDisk("hdd", "")
|
||||
// as in "dev/fd1"; refers floppy disk drive
|
||||
attachDisk("fd1", "")
|
||||
attachDisk("fd2", "")
|
||||
attachDisk("fd3", "")
|
||||
attachDisk("fd4", "")
|
||||
// SCSI connected optical drive
|
||||
attachDisk("sda", "")
|
||||
|
||||
// boot device
|
||||
computerValue["boot"] = "hda"
|
||||
}
|
||||
|
||||
fun getPeripheral(tableName: String): net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral? {
|
||||
peripheralTable.forEach {
|
||||
if (it?.tableName == tableName)
|
||||
return it
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getPeripheralSlot(tableName: String): Int? {
|
||||
peripheralTable.forEachIndexed { index, peri ->
|
||||
if (peri?.tableName == tableName)
|
||||
return index
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** @return installed slot */
|
||||
fun attachPeripheral(peri: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral): Int {
|
||||
(0..maxPeripherals - 1).forEach {
|
||||
try {
|
||||
attachPeripheralTo(peri, it)
|
||||
return it
|
||||
}
|
||||
catch (tryNext: RuntimeException) { }
|
||||
}
|
||||
|
||||
throw RuntimeException("No vacant peripheral slot")
|
||||
}
|
||||
|
||||
fun attachPeripheralTo(peri: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral, slot: Int) {
|
||||
if (peripheralTable[slot] == null) {
|
||||
peripheralTable[slot] = peri
|
||||
peri.loadLib(luaJ_globals)
|
||||
println("[TerrarumComputer] loading peripheral $peri")
|
||||
}
|
||||
else {
|
||||
throw RuntimeException("Peripheral slot is already taken by: ${peripheralTable[slot]?.tableName}")
|
||||
}
|
||||
}
|
||||
|
||||
fun detachPeripheral(peri: net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.Peripheral) {
|
||||
// search for the peripheral
|
||||
var found = -1
|
||||
for (i in 0..maxPeripherals - 1) {
|
||||
if (peripheralTable[i] == peri) {
|
||||
found = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (found >= 0) {
|
||||
peripheralTable[found] = null
|
||||
println("[TerrarumComputer] unloading peripheral $peri")
|
||||
}
|
||||
else {
|
||||
throw IllegalArgumentException("Peripheral not exists: $peri")
|
||||
}
|
||||
}
|
||||
|
||||
fun attachTerminal(term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) {
|
||||
this.term = term
|
||||
initSandbox(term)
|
||||
}
|
||||
|
||||
fun initSandbox(term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) {
|
||||
luaJ_globals = JsePlatform.debugGlobals()
|
||||
|
||||
stdout = net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.TerminalPrintStream(this)
|
||||
stderr = net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.TerminalPrintStream(this)
|
||||
stdin = net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.TerminalInputStream(this)
|
||||
|
||||
luaJ_globals.STDOUT = stdout
|
||||
luaJ_globals.STDERR = stderr
|
||||
luaJ_globals.STDIN = stdin
|
||||
|
||||
luaJ_globals["bit"] = luaJ_globals["bit32"]
|
||||
|
||||
// load libraries
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Term(luaJ_globals, term)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Security(luaJ_globals)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Filesystem(luaJ_globals, this)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.HostAccessProvider(luaJ_globals, this)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.Input(luaJ_globals, this)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.PcSpeakerDriver(luaJ_globals, this)
|
||||
net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi.WorldInformationProvider(luaJ_globals)
|
||||
|
||||
// secure the sandbox
|
||||
//luaJ_globals["io"] = LuaValue.NIL
|
||||
// dubug should be sandboxed in BOOT.lua (use OpenComputers code)
|
||||
//val sethook = luaJ_globals["debug"]["sethook"]
|
||||
//luaJ_globals["debug"] = LuaValue.NIL
|
||||
|
||||
// ROM BASIC
|
||||
val inputStream = javaClass.getResourceAsStream("/net/torvald/terrarum/modulecomputers/virtualcomputer/virtualcomputer/assets/lua/BOOT.lua")
|
||||
runCommand(InputStreamReader(inputStream), "=boot")
|
||||
|
||||
// computer-related global functions
|
||||
luaJ_globals["totalMemory"] = LuaFunGetTotalMem(this)
|
||||
|
||||
luaJ_globals["computer"] = LuaTable()
|
||||
// rest of the "computer" APIs should be implemented in BOOT.lua
|
||||
|
||||
|
||||
|
||||
// load every peripheral if we're in DEBUG
|
||||
if (DEBUG) {
|
||||
attachPeripheral(net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.PeripheralInternet(this))
|
||||
attachPeripheral(net.torvald.terrarum.modulecomputers.virtualcomputer.peripheral.PeripheralPSG(this))
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
fun update(delta: Float) {
|
||||
|
||||
if (currentExecutionThread.state == Thread.State.TERMINATED) {
|
||||
threadRun = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (!isHalted) {
|
||||
runBeepQueueManager(delta)
|
||||
}
|
||||
}
|
||||
|
||||
fun keyPressed(key: Int, c: Char) {
|
||||
stdinInput = c.toInt()
|
||||
|
||||
// wake thread
|
||||
runnableRunCommand.resume()
|
||||
|
||||
synchronized(stdin!!) {
|
||||
(stdin as java.lang.Object).notifyAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun openStdin() {
|
||||
stdinInput = -1
|
||||
// sleep the thread
|
||||
runnableRunCommand.pause()
|
||||
}
|
||||
|
||||
lateinit var currentExecutionThread: Thread
|
||||
private set
|
||||
lateinit var runnableRunCommand: ThreadRunCommand
|
||||
private set
|
||||
private var threadRun = false
|
||||
|
||||
fun runCommand(line: String, env: String) {
|
||||
if (!threadRun) {
|
||||
runnableRunCommand = ThreadRunCommand(luaJ_globals, line, env)
|
||||
currentExecutionThread = Thread(null, runnableRunCommand, "LuaJ Separated")
|
||||
currentExecutionThread.start()
|
||||
threadRun = true
|
||||
}
|
||||
}
|
||||
|
||||
fun runCommand(reader: Reader, filename: String) {
|
||||
if (!threadRun) {
|
||||
runnableRunCommand = ThreadRunCommand(luaJ_globals, reader, filename)
|
||||
currentExecutionThread = Thread(null, runnableRunCommand, "LuaJ Separated")
|
||||
currentExecutionThread.start()
|
||||
threadRun = true
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadRunCommand : Runnable {
|
||||
|
||||
private val mode: Int
|
||||
private val arg1: Any
|
||||
private val arg2: String
|
||||
private val lua: Globals
|
||||
|
||||
@Volatile private var running = true
|
||||
@Volatile private var paused = false
|
||||
private val pauseLock = java.lang.Object()
|
||||
|
||||
constructor(luaInstance: Globals, line: String, env: String) {
|
||||
mode = 0
|
||||
arg1 = line
|
||||
arg2 = env
|
||||
lua = luaInstance
|
||||
}
|
||||
|
||||
constructor(luaInstance: Globals, reader: Reader, filename: String) {
|
||||
mode = 1
|
||||
arg1 = reader
|
||||
arg2 = filename
|
||||
lua = luaInstance
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
synchronized(pauseLock) {
|
||||
if (!running) { // may have changed while waiting to
|
||||
// synchronize on pauseLock
|
||||
return
|
||||
}
|
||||
if (paused) {
|
||||
try {
|
||||
pauseLock.wait() // will cause this Thread to block until
|
||||
// another thread calls pauseLock.notifyAll()
|
||||
// Note that calling wait() will
|
||||
// relinquish the synchronized lock that this
|
||||
// thread holds on pauseLock so another thread
|
||||
// can acquire the lock to call notifyAll()
|
||||
// (link with explanation below this code)
|
||||
}
|
||||
catch (ex: InterruptedException) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!running) { // running might have changed since we paused
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
val chunk: LuaValue
|
||||
if (mode == 0)
|
||||
chunk = lua.load(arg1 as String, arg2)
|
||||
else if (mode == 1)
|
||||
chunk = lua.load(arg1 as Reader, arg2)
|
||||
else
|
||||
throw IllegalArgumentException("Unsupported mode: $mode")
|
||||
|
||||
|
||||
chunk.call()
|
||||
}
|
||||
catch (e: LuaError) {
|
||||
e.printStackTrace(System.err)
|
||||
//lua.STDERR.println("${SimpleTextTerminal.ASCII_DLE}${e.message}${SimpleTextTerminal.ASCII_DC4}")
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
running = false
|
||||
// you might also want to do this:
|
||||
//interrupt()
|
||||
}
|
||||
|
||||
fun pause() {
|
||||
// you may want to throw an IllegalStateException if !running
|
||||
paused = true
|
||||
}
|
||||
|
||||
fun resume() {
|
||||
synchronized(pauseLock) {
|
||||
paused = false
|
||||
pauseLock.notifyAll() // Unblocks thread
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LuaFunGetTotalMem(val computer: TerrarumComputer) : ZeroArgFunction() {
|
||||
override fun call(): LuaValue {
|
||||
return LuaValue.valueOf(computer.memSize)
|
||||
}
|
||||
}
|
||||
|
||||
class ComputerEmitTone(val computer: TerrarumComputer) : TwoArgFunction() {
|
||||
override fun call(millisec: LuaValue, freq: LuaValue): LuaValue {
|
||||
computer.playTone(millisec.checkdouble().toFloat(), freq.checkdouble())
|
||||
return LuaValue.NONE
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// BEEPER DRIVER //
|
||||
///////////////////
|
||||
|
||||
private val beepMaxLen = 10f
|
||||
// let's regard it as a tracker...
|
||||
private val beepQueue = ArrayList<Pair<Second, Double>>()
|
||||
private var beepCursor = -1
|
||||
private var beepQueueLineExecTimer: Second = 0f
|
||||
private var beepQueueFired = false
|
||||
|
||||
private fun runBeepQueueManager(delta: Float) {
|
||||
// start emitTone queue
|
||||
if (beepQueue.size > 0 && beepCursor == -1) {
|
||||
beepCursor = 0
|
||||
}
|
||||
|
||||
// advance emitTone queue
|
||||
if (beepCursor >= 0 && beepQueueLineExecTimer >= beepQueueGetLenOfPtn(beepCursor)) {
|
||||
beepQueueLineExecTimer -= beepQueueGetLenOfPtn(beepCursor)
|
||||
beepCursor += 1
|
||||
beepQueueFired = false
|
||||
}
|
||||
|
||||
// complete emitTone queue
|
||||
if (beepCursor >= beepQueue.size) {
|
||||
clearBeepQueue()
|
||||
}
|
||||
|
||||
// actually play queue
|
||||
if (beepCursor >= 0 && beepQueue.size > 0 && !beepQueueFired) {
|
||||
playTone(beepQueue[beepCursor].first, beepQueue[beepCursor].second)
|
||||
beepQueueFired = true
|
||||
|
||||
// delete sources that is finished. AL is limited to 256 sources. If you exceed it,
|
||||
// we won't get any more sounds played.
|
||||
AL10.alSourcei(oldBeepSource, AL10.AL_BUFFER, 0)
|
||||
AL10.alDeleteSources(oldBeepSource)
|
||||
AL10.alDeleteBuffers(oldBeepBuffer)
|
||||
}
|
||||
|
||||
if (beepQueueFired) beepQueueLineExecTimer += delta
|
||||
}
|
||||
|
||||
fun clearBeepQueue() {
|
||||
beepQueue.clear()
|
||||
beepCursor = -1
|
||||
beepQueueLineExecTimer = 0f
|
||||
|
||||
//AL.destroy()
|
||||
|
||||
if (DEBUG) println("[TerrarumComputer] !! Beep queue clear")
|
||||
}
|
||||
|
||||
fun enqueueBeep(duration: Double, freq: Double) {
|
||||
beepQueue.add(Pair(Math.min(duration.toFloat(), beepMaxLen), freq))
|
||||
}
|
||||
|
||||
fun beepQueueGetLenOfPtn(ptnIndex: Int) = beepQueue[ptnIndex].first
|
||||
|
||||
|
||||
////////////////////
|
||||
// TONE GENERATOR //
|
||||
////////////////////
|
||||
|
||||
private val sampleRate = 44100
|
||||
private var beepSource: Int = -1
|
||||
private var beepBuffer: Int = -1
|
||||
private var oldBeepSource: Int = -1
|
||||
private var oldBeepBuffer: Int = -1
|
||||
var audioData: ByteBuffer? = null
|
||||
|
||||
/**
|
||||
* @param duration : milliseconds
|
||||
* @param rampUp
|
||||
* @param rampDown
|
||||
*
|
||||
* ,---. (true, true) ,---- (true, false) ----. (false, true) ----- (false, false)
|
||||
*/
|
||||
private fun makeAudioData(duration: Second, freq: Double,
|
||||
rampUp: Boolean = true, rampDown: Boolean = true): ByteBuffer {
|
||||
|
||||
TODO("with duration as Seconds")
|
||||
|
||||
val audioDataSize = duration.times(sampleRate).ceilInt()
|
||||
val audioData = BufferUtils.createByteBuffer(audioDataSize)
|
||||
|
||||
/*val realDuration = duration * sampleRate / 1000
|
||||
val chopSize = freq / sampleRate
|
||||
|
||||
val amp = Math.max(4600.0 / freq, 1.0)
|
||||
val nHarmonics = if (freq >= 22050.0) 1
|
||||
else if (freq >= 11025.0) 2
|
||||
else if (freq >= 5512.5) 3
|
||||
else if (freq >= 2756.25) 4
|
||||
else if (freq >= 1378.125) 5
|
||||
else if (freq >= 689.0625) 6
|
||||
else 7
|
||||
|
||||
val transitionThre = 974.47218
|
||||
|
||||
// TODO volume ramping?
|
||||
if (freq == 0.0) {
|
||||
for (_ in 0..audioDataSize - 1) {
|
||||
audioData.put(0x00.toByte())
|
||||
}
|
||||
}
|
||||
else if (freq < transitionThre) { // chopper generator (for low freq)
|
||||
for (tsart in 0..audioDataSize - 1) {
|
||||
var sine: Double = amp * Math.cos(Math.PI * 2 * () * chopSize)
|
||||
if (sine > 0.79) sine = 0.79
|
||||
else if (sine < -0.79) sine = -0.79
|
||||
audioData.put(
|
||||
(0.5 + 0.5 * sine).times(0xFF).roundInt().toByte()
|
||||
)
|
||||
}
|
||||
}
|
||||
else { // harmonics generator (for high freq)
|
||||
for (x in 0..realDuration - 1) {
|
||||
var sine: Double = 0.0
|
||||
for (k in 1..nHarmonics) { // mix only odd harmonics in order to make a squarewave
|
||||
sine += Math.sin(Math.PI * 2 * (2*k - 1) * chopSize * x) / (2*k - 1)
|
||||
}
|
||||
audioData.put(
|
||||
(0.5 + 0.5 * sine).times(0xFF).roundInt().toByte()
|
||||
)
|
||||
}
|
||||
}*/
|
||||
|
||||
audioData.rewind()
|
||||
|
||||
return audioData
|
||||
}
|
||||
|
||||
private fun playTone(length: Second, freq: Double) {
|
||||
/*audioData = makeAudioData(leninmilli, freq)
|
||||
|
||||
|
||||
if (!AL.isCreated()) AL.create()
|
||||
|
||||
|
||||
// Clear error stack.
|
||||
AL10.alGetError()
|
||||
|
||||
oldBeepBuffer = beepBuffer
|
||||
beepBuffer = AL10.alGenBuffers()
|
||||
checkALError()
|
||||
|
||||
try {
|
||||
AL10.alBufferData(beepBuffer, AL10.AL_FORMAT_MONO8, audioData, sampleRate)
|
||||
checkALError()
|
||||
|
||||
oldBeepSource = beepSource
|
||||
beepSource = AL10.alGenSources()
|
||||
checkALError()
|
||||
|
||||
try {
|
||||
AL10.alSourceQueueBuffers(beepSource, beepBuffer)
|
||||
checkALError()
|
||||
|
||||
AL10.alSource3f(beepSource, AL10.AL_POSITION, 0f, 0f, 1f)
|
||||
AL10.alSourcef(beepSource, AL10.AL_REFERENCE_DISTANCE, 1f)
|
||||
AL10.alSourcef(beepSource, AL10.AL_MAX_DISTANCE, 1f)
|
||||
AL10.alSourcef(beepSource, AL10.AL_GAIN, 0.3f)
|
||||
checkALError()
|
||||
|
||||
AL10.alSourcePlay(beepSource)
|
||||
checkALError()
|
||||
}
|
||||
catch (e: ALException) {
|
||||
AL10.alDeleteSources(beepSource)
|
||||
}
|
||||
}
|
||||
catch (e: ALException) {
|
||||
AL10.alDeleteSources(beepSource)
|
||||
}*/
|
||||
}
|
||||
|
||||
// Custom implementation of Util.checkALError() that uses our custom exception.
|
||||
private fun checkALError() {
|
||||
val errorCode = AL10.alGetError()
|
||||
if (errorCode != AL10.AL_NO_ERROR) {
|
||||
throw net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.ALException(errorCode)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user