mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-10 02:24:05 +09:00
test play music from appdata/Custom/Music
This commit is contained in:
@@ -545,6 +545,8 @@ open class IngameInstance(val batch: FlippingSpriteBatch, val isMultiplayer: Boo
|
|||||||
|
|
||||||
fun onConfigChange() {
|
fun onConfigChange() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open val musicGovernor: MusicGovernor = MusicGovernor()
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun Lock.lock(body: () -> Unit) {
|
inline fun Lock.lock(body: () -> Unit) {
|
||||||
|
|||||||
38
src/net/torvald/terrarum/MusicGovernor.kt
Normal file
38
src/net/torvald/terrarum/MusicGovernor.kt
Normal 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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2029,18 +2029,20 @@ open class ActorWithBody : Actor {
|
|||||||
val particleCount = (collisionDamage / 24.0).pow(0.75)
|
val particleCount = (collisionDamage / 24.0).pow(0.75)
|
||||||
val trueParticleCount = particleCount.toInt() + (Math.random() < (particleCount % 1.0)).toInt()
|
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 feetTiles = getFeetTiles()
|
||||||
val feetTileIndices = feetTiles.indices.toList().toIntArray()
|
val feetTileIndices = feetTiles.indices.toList().toIntArray()
|
||||||
|
|
||||||
for (i in 0 until trueParticleCount) {
|
for (i in 0 until trueParticleCount) {
|
||||||
if (i % feetTiles.size == 0) feetTileIndices.shuffle()
|
if (i % feetTiles.size == 0) feetTileIndices.shuffle()
|
||||||
|
|
||||||
feetTiles[feetTileIndices[i % feetTiles.size]].second.let { tile ->
|
feetTiles[feetTileIndices[i % feetTiles.size]].second.let { tile ->
|
||||||
val px = hitbox.startX + Math.random() * hitbox.width
|
val px = hitbox.startX + Math.random() * hitbox.width
|
||||||
val py = hitbox.endY
|
val py = hitbox.endY
|
||||||
makeDust0(tile, px, py, particleCount, collisionDamage, vecSum)
|
makeDust0(tile, px, py, particleCount, collisionDamage, vecSum)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,6 +434,13 @@ open class GameWorld(
|
|||||||
setWireGraphOfUnsafe(blockAddr, tile, connection)
|
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) {
|
fun removeTileWire(x: Int, y: Int, tile: ItemID, bypassEvent: Boolean) {
|
||||||
val (x, y) = coerceXY(x, y)
|
val (x, y) = coerceXY(x, y)
|
||||||
val blockAddr = LandUtil.getBlockAddr(this, 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
|
override fun equals(other: Any?) = layerTerrain.ptr == (other as GameWorld).layerTerrain.ptr
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@Transient const val WALL = 1
|
|
||||||
@Transient const val TERRAIN = 0
|
@Transient const val TERRAIN = 0
|
||||||
|
@Transient const val WALL = 1
|
||||||
@Transient const val ORES = 2
|
@Transient const val ORES = 2
|
||||||
|
|
||||||
@Transient val TILES_SUPPORTED = ReferencingRanges.TILES.last + 1
|
@Transient val TILES_SUPPORTED = ReferencingRanges.TILES.last + 1
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
override var gameFullyLoaded = false
|
override var gameFullyLoaded = false
|
||||||
internal set
|
internal set
|
||||||
|
|
||||||
|
override val musicGovernor = TerrarumMusicGovernor()
|
||||||
|
|
||||||
|
|
||||||
//////////////
|
//////////////
|
||||||
@@ -883,12 +884,11 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
oldSelectedWireRenderClass = selectedWireRenderClass
|
oldSelectedWireRenderClass = selectedWireRenderClass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
musicGovernor.update(this, delta)
|
||||||
|
|
||||||
////////////////////////
|
////////////////////////
|
||||||
// ui-related updates //
|
// ui-related updates //
|
||||||
////////////////////////
|
////////////////////////
|
||||||
//uiContainer.forEach { it.update(delta) }
|
|
||||||
//debugWindow.update(delta)
|
|
||||||
//notifier.update(delta)
|
|
||||||
// open/close fake blur UI according to what's opened
|
// open/close fake blur UI according to what's opened
|
||||||
if (uiInventoryPlayer.isVisible ||
|
if (uiInventoryPlayer.isVisible ||
|
||||||
getUIFixture.get()?.isVisible == true || worldTransitionOngoing) {
|
getUIFixture.get()?.isVisible == true || worldTransitionOngoing) {
|
||||||
@@ -1520,6 +1520,8 @@ open class TerrarumIngame(batch: FlippingSpriteBatch) : IngameInstance(batch) {
|
|||||||
catch (e: IllegalArgumentException) {}
|
catch (e: IllegalArgumentException) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
musicGovernor.dispose()
|
||||||
|
|
||||||
super.dispose()
|
super.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
132
src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt
Normal file
132
src/net/torvald/terrarum/modulebasegame/TerrarumMusicGovernor.kt
Normal 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() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import com.badlogic.gdx.utils.JsonValue
|
|||||||
import net.torvald.terrarum.TerrarumAppConfiguration
|
import net.torvald.terrarum.TerrarumAppConfiguration
|
||||||
import net.torvald.terrarum.gameitems.ItemID
|
import net.torvald.terrarum.gameitems.ItemID
|
||||||
import net.torvald.terrarum.gameworld.BlockLayerI16
|
import net.torvald.terrarum.gameworld.BlockLayerI16
|
||||||
|
import net.torvald.terrarum.gameworld.GameWorld
|
||||||
|
import net.torvald.terrarum.linearSearchBy
|
||||||
import net.torvald.terrarum.utils.HashArray
|
import net.torvald.terrarum.utils.HashArray
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,16 +49,31 @@ class PointOfInterest(
|
|||||||
override fun read(json: Json, jsonData: JsonValue) {
|
override fun read(json: Json, jsonData: JsonValue) {
|
||||||
TODO("Not yet implemented")
|
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(
|
class POILayer(
|
||||||
name: String
|
name: String
|
||||||
) : Json.Serializable {
|
) : Json.Serializable {
|
||||||
constructor() : this("undefined")
|
constructor() : this("undefined")
|
||||||
|
|
||||||
@Transient val name = name
|
@Transient val name = name
|
||||||
@Transient val blockLayer = ArrayList<BlockLayerI16>()
|
@Transient internal lateinit var blockLayer: ArrayList<BlockLayerI16>
|
||||||
@Transient private lateinit var dat: Array<ByteArray>
|
@Transient internal lateinit var dat: Array<ByteArray>
|
||||||
|
|
||||||
fun getUniqueTiles(): List<Int> {
|
fun getUniqueTiles(): List<Int> {
|
||||||
return blockLayer.flatMap { layer ->
|
return blockLayer.flatMap { layer ->
|
||||||
@@ -69,6 +86,9 @@ class POILayer(
|
|||||||
*
|
*
|
||||||
* Tilenum: tile number in the block layer, identical to the any other block layers
|
* 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
|
* 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) {
|
fun getReadyForSerialisation(tilenumToItemID: Map<Int, ItemID>, itemIDtoTileSym: Map<ItemID, Long>, byteLength: Int) {
|
||||||
dat = blockLayer.map { layer ->
|
dat = blockLayer.map { layer ->
|
||||||
@@ -87,10 +107,15 @@ class POILayer(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts `dat` into `blockLayer` so the Layer can be actually utilised.
|
* 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) {
|
fun getReadyToBeUsed(tileSymbolToItemId: HashArray<ItemID>, itemIDtoTileNum: Map<ItemID, Int>, width: Int, height: Int, byteLength: Int) {
|
||||||
blockLayer.forEach { it.dispose() }
|
if (::blockLayer.isInitialized) {
|
||||||
blockLayer.clear()
|
blockLayer.forEach { it.dispose() }
|
||||||
|
}
|
||||||
|
blockLayer = ArrayList<BlockLayerI16>()
|
||||||
|
|
||||||
dat.forEachIndexed { layerIndex, layer ->
|
dat.forEachIndexed { layerIndex, layer ->
|
||||||
val currentBlockLayer = BlockLayerI16(width, height).also {
|
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) {
|
override fun write(json: Json) {
|
||||||
|
if (!::dat.isInitialized) throw IllegalStateException("Internal data is not prepared! please run getReadyForSerialisation() before writing!")
|
||||||
json.setTypeName(null)
|
json.setTypeName(null)
|
||||||
json.writeValue("name", name)
|
json.writeValue("name", name)
|
||||||
json.writeValue("dat", dat)
|
json.writeValue("dat", dat)
|
||||||
|
|||||||
Reference in New Issue
Block a user