test play music from appdata/Custom/Music

This commit is contained in:
minjaesong
2023-10-21 15:08:55 +09:00
parent d57f23d4f1
commit f37a28eb17
7 changed files with 241 additions and 17 deletions

View File

@@ -545,6 +545,8 @@ open class IngameInstance(val batch: FlippingSpriteBatch, val isMultiplayer: Boo
fun onConfigChange() {
}
open val musicGovernor: MusicGovernor = MusicGovernor()
}
inline fun Lock.lock(body: () -> Unit) {

View File

@@ -0,0 +1,38 @@
package net.torvald.terrarum
open class MusicGovernor {
open fun update(ingameInstance: IngameInstance, delta: Float) {
}
protected var state = 0 // 0: disabled, 1: playing, 2: waiting
protected var intermissionAkku = 0f
protected var intermissionLength = 1f
protected var musicFired = false
protected var fadeoutAkku = 0f
protected var fadeoutLength = 0f
protected var fadeoutFired = false
protected var fadeinFired = false
fun requestFadeOut(length: Float) {
if (!fadeoutFired) {
fadeoutLength = length
fadeoutAkku = 0f
fadeoutFired = true
}
}
fun requestFadeIn(length: Float) {
if (!fadeoutFired) {
fadeoutLength = length
fadeoutAkku = 0f
fadeinFired = true
}
}
open fun dispose() {
}
}

View File

@@ -2029,18 +2029,20 @@ open class ActorWithBody : Actor {
val particleCount = (collisionDamage / 24.0).pow(0.75)
val trueParticleCount = particleCount.toInt() + (Math.random() < (particleCount % 1.0)).toInt()
if (collisionDamage > 1.0 / 1024.0) printdbg(this, "Collision damage: $collisionDamage N, count: $particleCount, velocity: $vecSum, mass: ${this.mass}")
if (collisionDamage > 1.0 / 1024.0) {
// printdbg(this, "Collision damage: $collisionDamage N, count: $particleCount, velocity: $vecSum, mass: ${this.mass}")
val feetTiles = getFeetTiles()
val feetTileIndices = feetTiles.indices.toList().toIntArray()
val feetTiles = getFeetTiles()
val feetTileIndices = feetTiles.indices.toList().toIntArray()
for (i in 0 until trueParticleCount) {
if (i % feetTiles.size == 0) feetTileIndices.shuffle()
for (i in 0 until trueParticleCount) {
if (i % feetTiles.size == 0) feetTileIndices.shuffle()
feetTiles[feetTileIndices[i % feetTiles.size]].second.let { tile ->
val px = hitbox.startX + Math.random() * hitbox.width
val py = hitbox.endY
makeDust0(tile, px, py, particleCount, collisionDamage, vecSum)
feetTiles[feetTileIndices[i % feetTiles.size]].second.let { tile ->
val px = hitbox.startX + Math.random() * hitbox.width
val py = hitbox.endY
makeDust0(tile, px, py, particleCount, collisionDamage, vecSum)
}
}
}
}

View File

@@ -434,6 +434,13 @@ open class GameWorld(
setWireGraphOfUnsafe(blockAddr, tile, connection)
}
fun setTileOnLayerUnsafe(layer: Int, x: Int, y: Int, tile: Int) {
(getLayer(layer) ?: throw IllegalArgumentException("Unknown layer index: $layer")).let {
if (it !is BlockLayerI16) throw IllegalArgumentException("Block layers other than BlockLayer16 is not supported yet)")
it.unsafeSetTile(x, y, tile)
}
}
fun removeTileWire(x: Int, y: Int, tile: ItemID, bypassEvent: Boolean) {
val (x, y) = coerceXY(x, y)
val blockAddr = LandUtil.getBlockAddr(this, x, y)
@@ -790,8 +797,8 @@ open class GameWorld(
override fun equals(other: Any?) = layerTerrain.ptr == (other as GameWorld).layerTerrain.ptr
companion object {
@Transient const val WALL = 1
@Transient const val TERRAIN = 0
@Transient const val WALL = 1
@Transient const val ORES = 2
@Transient val TILES_SUPPORTED = ReferencingRanges.TILES.last + 1

View File

@@ -258,6 +258,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
override var gameFullyLoaded = false
internal set
override val musicGovernor = TerrarumMusicGovernor()
//////////////
@@ -883,12 +884,11 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
oldSelectedWireRenderClass = selectedWireRenderClass
}
musicGovernor.update(this, delta)
////////////////////////
// ui-related updates //
////////////////////////
//uiContainer.forEach { it.update(delta) }
//debugWindow.update(delta)
//notifier.update(delta)
// open/close fake blur UI according to what's opened
if (uiInventoryPlayer.isVisible ||
getUIFixture.get()?.isVisible == true || worldTransitionOngoing) {
@@ -1520,6 +1520,8 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
catch (e: IllegalArgumentException) {}
}
musicGovernor.dispose()
super.dispose()
}
}

View File

@@ -0,0 +1,132 @@
package net.torvald.terrarum.modulebasegame
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.audio.Music
import com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.terrarum.App
import net.torvald.terrarum.App.printdbg
import net.torvald.terrarum.IngameInstance
import net.torvald.terrarum.MusicGovernor
import net.torvald.terrarum.tryDispose
import java.io.File
data class MusicContainer(
val name: String,
val file: File,
val gdxMusic: Music
) {
override fun toString() = if (name.isEmpty()) file.nameWithoutExtension else name
}
class TerrarumMusicGovernor : MusicGovernor() {
private val songs: List<MusicContainer> =
File(App.customMusicDir).listFiles()?.mapNotNull {
printdbg(this, "Music: ${it.absolutePath}")
try {
MusicContainer(
it.nameWithoutExtension,
it,
Gdx.audio.newMusic(Gdx.files.absolute(it.absolutePath))
)
}
catch (e: GdxRuntimeException) {
e.printStackTrace()
null
}
} ?: emptyList() // TODO test code
private var currentMusic: MusicContainer? = null
private var musicBin: ArrayList<Int> = ArrayList(songs.indices.toList().shuffled())
private fun stopMusic() {
printdbg(this, "Now stopping: $currentMusic")
state = 2
intermissionAkku = 0f
intermissionLength = 30f + 60f * Math.random().toFloat() // 30s-90m
musicFired = false
currentMusic = null
fadeoutFired = false
printdbg(this, "Intermission: $intermissionLength seconds")
}
private var warningPrinted = false
private val musicVolume: Float
get() = (App.getConfigDouble("musicvolume") * App.getConfigDouble("mastervolume")).toFloat()
override fun update(ingame: IngameInstance, delta: Float) {
if (songs.isEmpty()) {
if (!warningPrinted) {
warningPrinted = true
printdbg(this, "Warning: songs list is empty")
}
return
}
val ingame = ingame as TerrarumIngame
if (state == 0) state = 2
when (state) {
1 -> {
if (!musicFired) {
musicFired = true
val song = songs[musicBin.removeAt(0)]
// prevent same song to play twice
if (musicBin.isEmpty()) {
musicBin = ArrayList(songs.indices.toList().shuffled())
}
song.gdxMusic.volume = musicVolume
song.gdxMusic.play()
printdbg(this, "Now playing: $song")
currentMusic = song
// process fadeout request
if (fadeoutFired) {
fadeoutAkku += delta
currentMusic?.gdxMusic?.volume = 1f - musicVolume * (fadeoutAkku / fadeoutLength)
if (fadeoutAkku >= fadeoutLength) {
currentMusic?.gdxMusic?.pause()
}
}
// process fadein request
else if (fadeinFired) {
if (currentMusic?.gdxMusic?.isPlaying == false) {
currentMusic?.gdxMusic?.play()
}
fadeoutAkku += delta
currentMusic?.gdxMusic?.volume = musicVolume * (fadeoutAkku / fadeoutLength)
}
}
else {
if (currentMusic?.gdxMusic?.isPlaying == false) {
stopMusic()
}
}
}
2 -> {
intermissionAkku += delta
if (intermissionAkku >= intermissionLength) {
intermissionAkku = 0f
state = 1
}
}
}
}
override fun dispose() {
currentMusic?.gdxMusic?.stop()
stopMusic()
songs.forEach { it.gdxMusic.tryDispose() }
}
}

View File

@@ -5,6 +5,8 @@ import com.badlogic.gdx.utils.JsonValue
import net.torvald.terrarum.TerrarumAppConfiguration
import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarum.gameworld.BlockLayerI16
import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.linearSearchBy
import net.torvald.terrarum.utils.HashArray
/**
@@ -47,16 +49,31 @@ class PointOfInterest(
override fun read(json: Json, jsonData: JsonValue) {
TODO("Not yet implemented")
}
/**
* Place the specified layers onto the world. The name of the layers are case-insensitive.
*/
fun placeOnWorld(layerNames: List<String>, world: GameWorld, bottomCentreX: Int, bottomCenterY: Int) {
val layers = layerNames.map { name ->
(layers.linearSearchBy { it.name.equals(name, true) } ?: throw IllegalArgumentException("Layer with name '$name' not found"))
}
layers.forEach {
it.placeOnWorld(world, bottomCentreX, bottomCenterY)
}
}
}
/**
* @param name name of the layer, case-insensitive.
*/
class POILayer(
name: String
) : Json.Serializable {
constructor() : this("undefined")
@Transient val name = name
@Transient val blockLayer = ArrayList<BlockLayerI16>()
@Transient private lateinit var dat: Array<ByteArray>
@Transient internal lateinit var blockLayer: ArrayList<BlockLayerI16>
@Transient internal lateinit var dat: Array<ByteArray>
fun getUniqueTiles(): List<Int> {
return blockLayer.flatMap { layer ->
@@ -69,6 +86,9 @@ class POILayer(
*
* Tilenum: tile number in the block layer, identical to the any other block layers
* TileSymbol: condensed version of the Tilenum, of which the higheset number is equal to the number of unique tiles used in the layer
*
* `tilenumToItemID[-1]` should return `Block.NULL`
* `itemIDtoTileSym[Block.NULL]` should return -1, so that it would return 0xFF on any length of the word
*/
fun getReadyForSerialisation(tilenumToItemID: Map<Int, ItemID>, itemIDtoTileSym: Map<ItemID, Long>, byteLength: Int) {
dat = blockLayer.map { layer ->
@@ -87,10 +107,15 @@ class POILayer(
/**
* Converts `dat` into `blockLayer` so the Layer can be actually utilised.
*
* `itemIDtoTileNum[Block.NULL]` should return `-1`
* `tileSymbolToItemId[255]` and `tileSymbolToItemId[65535]` should return `Block.NULL`
*/
fun getReadyToBeUsed(tileSymbolToItemId: HashArray<ItemID>, itemIDtoTileNum: Map<ItemID, Int>, width: Int, height: Int, byteLength: Int) {
blockLayer.forEach { it.dispose() }
blockLayer.clear()
if (::blockLayer.isInitialized) {
blockLayer.forEach { it.dispose() }
}
blockLayer = ArrayList<BlockLayerI16>()
dat.forEachIndexed { layerIndex, layer ->
val currentBlockLayer = BlockLayerI16(width, height).also {
@@ -106,7 +131,23 @@ class POILayer(
}
}
fun placeOnWorld(world: GameWorld, bottomCentreX: Int, bottomCenterY: Int) {
val topLeftX = bottomCentreX - blockLayer[0].width / 2
val topLeftY = bottomCenterY - blockLayer[0].height
blockLayer.forEachIndexed { layerIndex, layer ->
for (x in 0 until layer.width) { for (y in 0 until layer.height) {
val tile = layer.unsafeGetTile(x, y)
if (tile != -1) {
val (wx, wy) = world.coerceXY(x + topLeftX, y + topLeftY)
world.setTileOnLayerUnsafe(layerIndex, wx, wy, tile)
}
} }
}
}
override fun write(json: Json) {
if (!::dat.isInitialized) throw IllegalStateException("Internal data is not prepared! please run getReadyForSerialisation() before writing!")
json.setTypeName(null)
json.writeValue("name", name)
json.writeValue("dat", dat)