From f37a28eb173a7d65a11eef78db7855a6f1e3380e Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 21 Oct 2023 15:08:55 +0900 Subject: [PATCH] test play music from appdata/Custom/Music --- src/net/torvald/terrarum/IngameInstance.kt | 2 + src/net/torvald/terrarum/MusicGovernor.kt | 38 +++++ .../terrarum/gameactors/ActorWithBody.kt | 20 +-- .../torvald/terrarum/gameworld/GameWorld.kt | 9 +- .../terrarum/modulebasegame/TerrarumIngame.kt | 8 +- .../modulebasegame/TerrarumMusicGovernor.kt | 132 ++++++++++++++++++ .../terrarum/serialise/PointOfInterest.kt | 49 ++++++- 7 files changed, 241 insertions(+), 17 deletions(-) create mode 100644 src/net/torvald/terrarum/MusicGovernor.kt create mode 100644 src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt diff --git a/src/net/torvald/terrarum/IngameInstance.kt b/src/net/torvald/terrarum/IngameInstance.kt index f1f7744a3..58464f918 100644 --- a/src/net/torvald/terrarum/IngameInstance.kt +++ b/src/net/torvald/terrarum/IngameInstance.kt @@ -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) { diff --git a/src/net/torvald/terrarum/MusicGovernor.kt b/src/net/torvald/terrarum/MusicGovernor.kt new file mode 100644 index 000000000..f96d3e2ca --- /dev/null +++ b/src/net/torvald/terrarum/MusicGovernor.kt @@ -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() { + + } + +} diff --git a/src/net/torvald/terrarum/gameactors/ActorWithBody.kt b/src/net/torvald/terrarum/gameactors/ActorWithBody.kt index eca86c97e..94e1ea6f5 100644 --- a/src/net/torvald/terrarum/gameactors/ActorWithBody.kt +++ b/src/net/torvald/terrarum/gameactors/ActorWithBody.kt @@ -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) + } } } } diff --git a/src/net/torvald/terrarum/gameworld/GameWorld.kt b/src/net/torvald/terrarum/gameworld/GameWorld.kt index 559f9f349..fa3fd7e2b 100644 --- a/src/net/torvald/terrarum/gameworld/GameWorld.kt +++ b/src/net/torvald/terrarum/gameworld/GameWorld.kt @@ -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 diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt index f29748dfc..fcab3bc4b 100644 --- a/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumIngame.kt @@ -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() } } diff --git a/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt new file mode 100644 index 000000000..50a7ba104 --- /dev/null +++ b/src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt @@ -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 = + 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 = 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() } + } +} diff --git a/src/net/torvald/terrarum/serialise/PointOfInterest.kt b/src/net/torvald/terrarum/serialise/PointOfInterest.kt index 2ac883d5e..21f3e9285 100644 --- a/src/net/torvald/terrarum/serialise/PointOfInterest.kt +++ b/src/net/torvald/terrarum/serialise/PointOfInterest.kt @@ -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, 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() - @Transient private lateinit var dat: Array + @Transient internal lateinit var blockLayer: ArrayList + @Transient internal lateinit var dat: Array fun getUniqueTiles(): List { 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, itemIDtoTileSym: Map, 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, itemIDtoTileNum: Map, width: Int, height: Int, byteLength: Int) { - blockLayer.forEach { it.dispose() } - blockLayer.clear() + if (::blockLayer.isInitialized) { + blockLayer.forEach { it.dispose() } + } + blockLayer = ArrayList() 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)