more sounds

This commit is contained in:
Bixilon 2021-05-24 18:25:24 +02:00
parent 52f873c5f8
commit 504302025f
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
11 changed files with 182 additions and 68 deletions

View File

@ -26,6 +26,7 @@ import de.bixilon.minosoft.gui.rendering.chunk.models.renderable.ElementRenderer
import de.bixilon.minosoft.gui.rendering.shader.Shader
import de.bixilon.minosoft.gui.rendering.util.VecUtil
import de.bixilon.minosoft.gui.rendering.util.VecUtil.getWorldOffset
import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec3
import de.bixilon.minosoft.protocol.network.connection.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.BitByte.isBit
@ -165,7 +166,7 @@ class BlockOutlineRenderer(
collisionMesh?.unload()
outlineMesh = BlockOutlineMesh()
val blockOffset = raycastHit.blockPosition.getWorldOffset(raycastHit.blockState.block).plus(raycastHit.blockPosition)
val blockOffset = raycastHit.blockPosition.toVec3 + raycastHit.blockPosition.getWorldOffset(raycastHit.blockState.block)
drawVoxelShape(raycastHit.blockState.outlineShape, blockOffset, outlineMesh)
outlineMesh.load()

View File

@ -29,6 +29,7 @@ import de.bixilon.minosoft.gui.rendering.textures.TextureTransparencies
import de.bixilon.minosoft.gui.rendering.util.VecUtil
import de.bixilon.minosoft.gui.rendering.util.VecUtil.plus
import de.bixilon.minosoft.gui.rendering.util.VecUtil.rotate
import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec3
import glm_.vec3.Vec3
class ElementRenderer(
@ -83,7 +84,7 @@ class ElementRenderer(
for ((drawPositionIndex, texturePositionIndex) in DRAW_ODER) {
val input = drawPositions[drawPositionIndex]
val output = context.blockPosition plus context.offset + input + DRAW_OFFSET
val output = context.blockPosition.toVec3 + input + DRAW_OFFSET + context.offset
mesh.addVertex(
position = output,
textureCoordinates = texturePositions[texturePositionIndex]!!,

View File

@ -29,6 +29,7 @@ import de.bixilon.minosoft.modding.event.events.BlockBreakAckEvent
import de.bixilon.minosoft.protocol.packets.c2s.play.ArmSwingC2SP
import de.bixilon.minosoft.protocol.packets.c2s.play.BlockBreakC2SP
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.KUtil.asResourceLocation
import de.bixilon.minosoft.util.KUtil.synchronizedMapOf
import glm_.pow
import glm_.vec3.Vec3i
@ -131,6 +132,8 @@ class LeftClickHandler(
connection.sendPacket(BlockBreakC2SP(BlockBreakC2SP.BreakType.FINISHED_DIGGING, raycastHit.blockPosition, raycastHit.hitDirection))
clearDigging()
connection.world.setBlockState(raycastHit.blockPosition, null)
renderWindow.rendering.audioPlayer.playSoundEvent(connection.registries.soundEventRegistry[BLOCK_BREAK_SOUND]!!) // ToDO
}
val canStartBreaking = currentTime - breakSent >= ProtocolDefinition.TICK_TIME
@ -268,4 +271,8 @@ class LeftClickHandler(
}
swingArm()
}
companion object {
val BLOCK_BREAK_SOUND = "minecraft:block.metal.break".asResourceLocation()
}
}

View File

@ -22,19 +22,20 @@ import de.bixilon.minosoft.gui.rendering.Rendering
import de.bixilon.minosoft.gui.rendering.sound.sounds.Sound
import de.bixilon.minosoft.gui.rendering.sound.sounds.SoundList
import de.bixilon.minosoft.protocol.network.connection.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ConnectionStates
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.util.CountUpAndDownLatch
import de.bixilon.minosoft.util.KUtil.asResourceLocation
import de.bixilon.minosoft.util.KUtil.synchronizedListOf
import de.bixilon.minosoft.util.KUtil.toSynchronizedList
import de.bixilon.minosoft.util.Queue
import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import org.lwjgl.BufferUtils.createShortBuffer
import org.lwjgl.openal.AL
import org.lwjgl.openal.AL10.*
import org.lwjgl.openal.ALC
import org.lwjgl.openal.ALC10.*
import org.lwjgl.openal.EXTThreadLocalContext.alcSetThreadContext
import org.lwjgl.stb.STBVorbis.stb_vorbis_get_samples_short_interleaved
import org.lwjgl.system.MemoryUtil
import java.nio.ByteBuffer
import java.nio.IntBuffer
@ -53,16 +54,35 @@ class AudioPlayer(
private var device = 0L
private var context = 0L
private var source = 0
private val queue = Queue()
private lateinit var listener: SoundListener
private val sources: MutableList<SoundSource> = synchronizedListOf()
private var pcm: ShortBuffer? = null
private fun preloadSounds() {
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Preloading sounds..." }
if (SoundConstants.DISABLE_PRELOADING) {
return
}
for (soundList in sounds.values) {
for (sound in soundList.sounds) {
if (SoundConstants.PRELOAD_ALL_SOUNDS || sound.preload) {
sound.load(connection.assetsManager)
}
}
}
}
fun init(latch: CountUpAndDownLatch) {
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "Loading OpenAL..." }
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Loading sounds.json" }
loadSounds()
preloadSounds()
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Initializing OpenAL..." }
@ -77,59 +97,84 @@ class AudioPlayer(
val deviceCaps = ALC.createCapabilities(device)
AL.createCapabilities(deviceCaps)
val listener = SoundListener()
val source = SoundSource(false)
// Testing, ToDo
val sound = sounds[connection.registries.soundEventRegistry[0]]!!.sounds.iterator().next()
sound.load(connection.assetsManager)
listener = SoundListener()
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "OpenAL loaded!" }
val pcm = createShortBuffer(sound.samplesLength)
pcm.limit(stb_vorbis_get_samples_short_interleaved(sound.handle, sound.channels, pcm) * sound.channels)
val buffer = alGenBuffers()
alBufferData(buffer, sound.format, pcm, sound.sampleRate)
source.buffer = buffer
source.play()
while (source.isPlaying) {
Thread.sleep(1L)
}
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "Sound played!" }
initialized = true
latch.countDown()
}
fun playSoundEvent(soundEvent: SoundEvent) {
playSound(sounds[soundEvent]!!.getRandom())
}
private fun getAvailableSource(): SoundSource? {
for (source in sources.toSynchronizedList()) {
if (source.available) {
return source
}
}
// no source available
if (sources.size > SoundConstants.MAX_SOURCES_AMOUNT) {
return null
}
val source = SoundSource(false)
sources += source
return source
}
fun playSound(sound: Sound) {
queue += add@{
sound.load(connection.assetsManager)
if (sound.loadFailed) {
return@add
}
val source = getAvailableSource() ?: let {
Log.log(LogMessageType.RENDERING_GENERAL, LogLevels.WARN) { "Can not play sound: No source available!" }
return@add
}
source.sound = sound
source.play()
}
}
fun startLoop() {
while (connection.isConnected) {
while (connection.connectionState != ConnectionStates.DISCONNECTING) {
queue.work()
Thread.sleep(1L)
}
}
fun exit() {
// alDeleteBuffers(buffers)
alDeleteSources(source)
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "Unloading OpenAL..." }
//MemoryUtil.memFree(buffers)
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Unloading sounds..." }
for (soundList in sounds.values) {
for (sound in soundList.sounds) {
sound.unload()
}
}
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Unloading sources..." }
for (source in sources.toSynchronizedList()) {
source.unload()
}
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Destroying OpenAL context..." }
MemoryUtil.memFree(pcm)
alcSetThreadContext(MemoryUtil.NULL)
alcDestroyContext(context)
alcCloseDevice(device)
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.INFO) { "Unloaded OpenAL!" }
}
private fun loadSounds() {
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Loading sounds.json" }
val data = connection.assetsManager.readJsonAsset(SOUNDS_INDEX_FILE)
for ((soundEventResourceLocation, json) in data.entrySet()) {

View File

@ -0,0 +1,21 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.sound
object SoundConstants {
const val PRELOAD_ALL_SOUNDS = false
const val DISABLE_PRELOADING = false
const val MAX_SOURCES_AMOUNT = 32
}

View File

@ -24,6 +24,7 @@ class SoundListener(position: Vec3 = Vec3.EMPTY) {
alListener3f(AL_POSITION, value.x, value.y, value.z)
field = value
}
var velocity: Vec3 = Vec3.EMPTY
set(value) {
alListener3f(AL_VELOCITY, value.x, value.y, value.z)

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.gui.rendering.sound
import de.bixilon.minosoft.gui.rendering.sound.sounds.Sound
import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY
import glm_.vec3.Vec3
import org.lwjgl.openal.AL10.*
@ -52,15 +53,23 @@ class SoundSource(loop: Boolean = false) {
field = value
}
var buffer: Int = -1
var sound: Sound? = null
set(value) {
alSourcei(source, AL_BUFFER, value)
stop()
if (value?.loaded != true || value.loadFailed) {
field = null
return
}
alSourcei(source, AL_BUFFER, value.buffer)
field = value
}
val isPlaying: Boolean
get() = alGetSourcei(source, AL_SOURCE_STATE) == AL_PLAYING
val available: Boolean
get() = isPlaying
fun play() {
alSourcePlay(source)
}
@ -73,7 +82,7 @@ class SoundSource(loop: Boolean = false) {
alSourceStop(source)
}
fun delete() {
fun unload() {
stop()
alDeleteSources(source)
}

View File

@ -19,8 +19,7 @@ import de.bixilon.minosoft.util.logging.Log
import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
import org.lwjgl.BufferUtils
import org.lwjgl.openal.AL10.AL_FORMAT_MONO16
import org.lwjgl.openal.AL10.AL_FORMAT_STEREO16
import org.lwjgl.openal.AL10.*
import org.lwjgl.stb.STBVorbis.*
import org.lwjgl.stb.STBVorbisInfo
import org.lwjgl.system.MemoryUtil
@ -41,47 +40,74 @@ data class Sound(
private set
var loadFailed: Boolean = false
private set
var buffer: ByteBuffer? = null
var handle: Long = -1L
var channels: Int = -1
private set
var sampleRate: Int = -1
private set
var samplesLength: Int = -1
private set
var sampleSeconds: Float = -1.0f
private set
var buffer = -1
private set
var format: Int = -1
private var vorbisBuffer: ByteBuffer? = null
@Synchronized
fun load(assetsManager: AssetsManager) {
if (loaded || loadFailed) {
return
}
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.VERBOSE) { "Loading audio file: $path" }
try {
val buffer = assetsManager.readByteAsset(path)
this.buffer = buffer
val vorbisBuffer = assetsManager.readByteAsset(path)
this.vorbisBuffer = vorbisBuffer
val error = BufferUtils.createIntBuffer(1)
handle = stb_vorbis_open_memory(buffer, error, null)
if (handle == MemoryUtil.NULL) {
val vorbis = stb_vorbis_open_memory(vorbisBuffer, error, null)
if (vorbis == MemoryUtil.NULL) {
throw IllegalStateException("Can not load vorbis: ${path}: ${error[0]}")
}
val info = stb_vorbis_get_info(handle, STBVorbisInfo.malloc())
val info = stb_vorbis_get_info(vorbis, STBVorbisInfo.malloc())
channels = info.channels()
format = when (channels) {
val format = when (channels) {
1 -> AL_FORMAT_MONO16
2 -> AL_FORMAT_STEREO16
else -> TODO("Channels: $channels")
}
sampleRate = info.sample_rate()
samplesLength = stb_vorbis_stream_length_in_samples(handle)
sampleSeconds = stb_vorbis_stream_length_in_seconds(handle)
samplesLength = stb_vorbis_stream_length_in_samples(vorbis)
sampleSeconds = stb_vorbis_stream_length_in_seconds(vorbis)
val pcm = BufferUtils.createShortBuffer(samplesLength)
pcm.limit(stb_vorbis_get_samples_short_interleaved(vorbis, channels, pcm) * channels)
//ToDo: Somehow crashed?: MemoryUtil.memFree(vorbisBuffer)
this.buffer = alGenBuffers()
alBufferData(buffer, format, pcm, sampleRate)
loaded = true
} catch (exception: FileNotFoundException) {
loadFailed = true
Log.log(LogMessageType.RENDERING_LOADING, LogLevels.WARN) { "Can not load sound: $path: $exception" }
}
}
@Synchronized
fun unload() {
if (!loaded) {
return
}
alDeleteBuffers(buffer)
vorbisBuffer?.let { MemoryUtil.memFree(it) }
buffer = -1
channels = -1
sampleRate = -1
samplesLength = -1
sampleSeconds = -1.0f
loaded = false
}
}

View File

@ -148,14 +148,10 @@ object VecUtil {
}
val Vec3i.entityPosition: Vec3
get() {
return Vec3(x + 0.5f, y, z + 0.5f) // ToDo
}
get() = Vec3(x + 0.5f, y, z + 0.5f) // ToDo
val Vec3.blockPosition: Vec3i
get() {
return Vec3i((x - 0.5f).toInt(), y.toInt(), (z - 0.5f).toInt()) // ToDo
}
get() = Vec3i((x - 0.5f).toInt(), y.toInt(), (z - 0.5f).toInt()) // ToDo
fun Vec3i.Companion.of(chunkPosition: Vec2i, sectionHeight: Int, inChunkSectionPosition: Vec3i): Vec3i {
return Vec3i(
@ -198,8 +194,8 @@ object VecUtil {
fun Vec3i.getWorldOffset(block: Block): Vec3 {
if (block.randomOffsetType == null || !Minosoft.config.config.game.other.flowerRandomOffset) {
return EMPTY_VEC3
}
val positionHash = generatePositionHash(x, 0, z)
val maxModelOffset = 0.25f // ToDo: use block.model.max_model_offset
@ -239,18 +235,22 @@ object VecUtil {
position[axis].ceilInt - 1
}
}
fun getLengthMultiplier(direction: Vec3, position: Vec3, axis: Axes): Float {
return (getTarget(direction, position, axis) - position[axis]) / direction[axis]
}
val directionXDistance = getLengthMultiplier(direction, position, Axes.X)
val directionYDistance = getLengthMultiplier(direction, position, Axes.Y)
val directionZDistance = getLengthMultiplier(direction, position, Axes.Z)
return glm.min(directionXDistance, directionYDistance, directionZDistance)
}
val Vec3.min: Float get() = glm.min(this.x, this.y, this.z)
val Vec3.min: Float
get() = glm.min(this.x, this.y, this.z)
val Vec3.max: Float get() = glm.max(this.x, this.y, this.z)
val Vec3.max: Float
get() = glm.max(this.x, this.y, this.z)
val Vec3.signs: Vec3
get() {
@ -283,8 +283,11 @@ object VecUtil {
return minDistanceDirection
}
val Vec3i.toVec3: Vec3
get() = Vec3(this)
operator fun Vec3.get(axis: Axes): Float {
return when(axis) {
return when (axis) {
Axes.X -> this.x
Axes.Y -> this.y
Axes.Z -> this.z

View File

@ -26,7 +26,7 @@ import de.bixilon.minosoft.util.logging.LogLevels
import de.bixilon.minosoft.util.logging.LogMessageType
class ContainerOpenS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
val containerId = if (buffer.versionId <= V_1_16) { // ToDo: This is completely guessed
val containerId = if (buffer.versionId <= V_1_14) { // ToDo: This is completely guessed, it has changed between 1.13 and 1.14, same as #L38
buffer.readUnsignedByte()
} else {
buffer.readVarInt()
@ -35,7 +35,7 @@ class ContainerOpenS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() {
buffer.versionId < V_14W03B -> {
buffer.connection.registries.containerTypeRegistry[buffer.readUnsignedByte()]
}
buffer.versionId >= V_1_16 -> { // ToDo: This is completely guessed
buffer.versionId >= V_1_14 -> { // ToDo: This is completely guessed
buffer.connection.registries.containerTypeRegistry[buffer.readVarInt()]
}
else -> {

View File

@ -27,7 +27,7 @@ class Queue {
add(runnable)
}
fun work(maxJobs: Int) {
fun work(maxJobs: Int = Int.MAX_VALUE) {
var jobsDone = 0
for (runnable in queue.toSynchronizedList()) {
this.queue.remove(runnable)