diff --git a/assets/mods/basegame/audio/effects/throwing/throw_low_short.wav b/assets/mods/basegame/audio/effects/throwing/throw_low_short.wav new file mode 100644 index 000000000..e7e094f5e Binary files /dev/null and b/assets/mods/basegame/audio/effects/throwing/throw_low_short.wav differ diff --git a/src/net/torvald/terrarum/QuickDirtyLint.kt b/src/net/torvald/terrarum/QuickDirtyLint.kt index 283eeb489..de4eede1f 100644 --- a/src/net/torvald/terrarum/QuickDirtyLint.kt +++ b/src/net/torvald/terrarum/QuickDirtyLint.kt @@ -8,16 +8,11 @@ import kotlin.system.exitProcess /** * Created by minjaesong on 2023-05-22. */ -fun main() { - - val csiR = "\u001B[31m" - val csiG = "\u001B[32m" - val csi0 = "\u001B[m" - - val superClasses = listOf( - Actor::class.java, - GameItem::class.java - ) +class QuickDirtyLint { + private val csiBold = "\u001B[1m" + private val csiR = "\u001B[31m" + private val csiG = "\u001B[32m" + private val csi0 = "\u001B[m" val serialisablePrimitives = listOf( // comparison: exact match @@ -101,37 +96,102 @@ fun main() { "Ljava/util/List" to "java.util.List has no zero-arg constructor." ) - var retcode = 0 + val classGraph = ClassGraph().acceptPackages("net.torvald.terrarum")/*.verbose()*/.enableAllInfo().scan() - ClassGraph().acceptPackages("net.torvald.terrarum")/*.verbose()*/.enableAllInfo().scan().let { scan -> - val offendingFields = scan.allClasses.filter { classinfo -> - superClasses.any { classinfo.extendsSuperclass(it) || classinfo.name == it.canonicalName } && - !classaNonGrata.contains(classinfo.name) - }.flatMap { clazz -> - clazz.declaredFieldInfo.filter { field -> - !field.isTransient && - !field.isEnum && - !serialisablePrimitives.contains(field.typeSignatureOrTypeDescriptorStr) && - serialisableTypes.none { field.typeSignatureOrTypeDescriptorStr.startsWith(it) } + fun checkForNonSerialisableFields(): Int { + val superClasses = listOf( + Actor::class.java, + GameItem::class.java + ) + + var retcode = 0 + + classGraph.let { scan -> + val offendingFields = scan.allClasses.filter { classinfo -> + superClasses.any { classinfo.extendsSuperclass(it) || classinfo.name == it.canonicalName } && + !classaNonGrata.contains(classinfo.name) + }.flatMap { clazz -> + clazz.declaredFieldInfo.filter { field -> + !field.isTransient && + !field.isEnum && + !serialisablePrimitives.contains(field.typeSignatureOrTypeDescriptorStr) && + serialisableTypes.none { field.typeSignatureOrTypeDescriptorStr.startsWith(it) } + } + }.filter { + !classaNomenNonGrata.contains(it.name) && !it.name.startsWith("this") && !it.name.contains("\$delegate") + } + + if (offendingFields.isNotEmpty()) { + println("$csiBold$csiR= Classes with non-@Transient fields =$csi0\n") + } + + offendingFields.forEach { + println("$csiBold${it.name}$csi0\n" + + "\t${csiG}from: ${csi0}${it.className}\n" + + "\t${csiG}type: ${csi0}${it.typeSignatureOrTypeDescriptorStr}\n" + + "\t${csiG}remarks: ${csi0}${ + remarks.keys.filter { key -> + it.typeSignatureOrTypeDescriptorStr.startsWith( + key + ) + }.map { remarks[it] }.joinToString(" ") + }" + ) + retcode = 1 } - }.filter { - !classaNomenNonGrata.contains(it.name) && !it.name.startsWith("this") && !it.name.contains("\$delegate") } -// println(offendingFields) - - offendingFields.forEach { - println("\u001B[1m${it.name}\u001B[m\n" + - "\t${csiG}from: ${csi0}${it.className}\n" + - "\t${csiG}type: ${csi0}${it.typeSignatureOrTypeDescriptorStr}\n" + - "\t${csiG}remarks: ${csi0}${remarks.keys.filter { key -> it.typeSignatureOrTypeDescriptorStr.startsWith(key) }.map { remarks[it] }.joinToString(" ")}") - retcode = 1 + if (retcode != 0) { + println("\n${csiR}Having above classes as non-@Transient may cause savegame to not load!$csi0") } + + return retcode } - if (retcode != 0) { - println("\n${csiR}Having above classes as non-@Transient may cause savegame to not load!$csi0") + fun checkForNoZeroArgConstructor(): Int { + val superClasses = listOf( + Actor::class.java + ) + + var retcode = 0 + + classGraph.let { scan -> + val offendingClasses = scan.allClasses.filter { classinfo -> + superClasses.any { classinfo.extendsSuperclass(it) || classinfo.name == it.canonicalName } && + !classaNonGrata.contains(classinfo.name) + }.filter { + !classaNomenNonGrata.contains(it.name) && !it.name.startsWith("this") && !it.name.contains("\$delegate") && + !it.name.endsWith("$1") && !it.name.endsWith("$2") && !it.name.endsWith("$3") && + !it.name.endsWith("$4") && !it.name.endsWith("$5") && !it.name.endsWith("$6") + }.filter { + it.declaredConstructorInfo.none { it.parameterInfo.isEmpty() } + } + + if (offendingClasses.isNotEmpty()) { + println("$csiBold$csiR= Classes with no-zero-arg constructors =$csi0\n") + } + + offendingClasses.forEach { + println("$csiBold${it.name}$csi0\n") + retcode = 1 + } + } + + if (retcode != 0) { + println("\n${csiR}Having no zero-arg constructors for above classes will cause savegame to not load!$csi0") + } + + return retcode } - exitProcess(retcode) + fun main() { + val nonSerialisable = checkForNonSerialisableFields() + val noZeroArgConstructor = checkForNoZeroArgConstructor() + + exitProcess(nonSerialisable or noZeroArgConstructor) + } +} + +fun main() { + QuickDirtyLint().main() } \ No newline at end of file diff --git a/src/net/torvald/terrarum/audio/AudioBank.kt b/src/net/torvald/terrarum/audio/AudioBank.kt index f269c4487..d998a660a 100644 --- a/src/net/torvald/terrarum/audio/AudioBank.kt +++ b/src/net/torvald/terrarum/audio/AudioBank.kt @@ -19,7 +19,7 @@ abstract class AudioBank : Disposable { abstract val name: String - abstract val samplingRate: Int + abstract val samplingRate: Float abstract val channels: Int abstract val totalSizeInSamples: Long abstract fun currentPositionInSamples(): Long diff --git a/src/net/torvald/terrarum/audio/AudioProcessBuf.kt b/src/net/torvald/terrarum/audio/AudioProcessBuf.kt index e61b4bb31..38ac968eb 100644 --- a/src/net/torvald/terrarum/audio/AudioProcessBuf.kt +++ b/src/net/torvald/terrarum/audio/AudioProcessBuf.kt @@ -4,6 +4,7 @@ import com.jme3.math.FastMath import net.torvald.terrarum.App import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATE +import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF import net.torvald.terrarum.ceilToInt import net.torvald.terrarum.floorToInt import net.torvald.terrarum.serialise.toUint @@ -25,7 +26,7 @@ private data class Frac(var nom: Int, val denom: Int) { * * Created by minjaesong on 2023-11-17. */ -class AudioProcessBuf(val inputSamplingRate: Int, val audioReadFun: (FloatArray, FloatArray) -> Int?, val onAudioFinished: () -> Unit) { +class AudioProcessBuf(val inputSamplingRate: Float, val audioReadFun: (FloatArray, FloatArray) -> Int?, val onAudioFinished: () -> Unit) { var pitch: Float = 1f var playbackSpeed = 1f @@ -48,6 +49,10 @@ class AudioProcessBuf(val inputSamplingRate: Int, val audioReadFun: (FloatArray, else 1f / (1f - this) } + /** + * PlayRate is varying value for simulation of Doppler Shift, etc., if all you want is to just change + * the pitch of the entire audio, override the sampling rate of the [MusicContainer]. + */ internal val playRate: Float get() = (playbackSpeed * pitch).coerceIn(0.5f, 2f)/*(playbackSpeed * when (jitterMode) { // disabled until arraycopy negative length bug is resolved 1 -> jitterMode1(totalSamplesPlayed.toFloat()) @@ -58,7 +63,7 @@ class AudioProcessBuf(val inputSamplingRate: Int, val audioReadFun: (FloatArray, get() = inputSamplingRate * playRate private val doResample - get() = !(inputSamplingRate == SAMPLING_RATE && (playRate - 1f).absoluteValue < (1f / 1024f)) + get() = !(inputSamplingRate == SAMPLING_RATEF && (playRate - 1f).absoluteValue < (1f / 1024f)) companion object { private val RPM = 45f @@ -90,6 +95,8 @@ class AudioProcessBuf(val inputSamplingRate: Int, val audioReadFun: (FloatArray, private val bufLut = HashMap, Int>() + private val bufferRates = arrayOf(48000,44100,32768,32000,24000,22050,16384,16000,12000,11025,8192,8000) + init { val bl = arrayOf( 1152,1380,1814,1792,2304,2634,3502,3456,4608,5141,6874,6912, @@ -99,14 +106,17 @@ class AudioProcessBuf(val inputSamplingRate: Int, val audioReadFun: (FloatArray, 4096,4554,5421,5376,6144,7056,8796,8704,10240,12078,15544,15360 ) - arrayOf(48000,44100,32768,32000,24000,22050,16384,16000,12000,11025,8192,8000).forEachIndexed { ri, r -> + bufferRates.forEachIndexed { ri, r -> arrayOf(128,256,512,1024,2048).forEachIndexed { bi, b -> - bufLut[b to r] = bl[bi * 12 + ri] * 2 + bufLut[b to r] = (bl[bi * 12 + ri] * 1.05).ceilToInt() * 2 } } } - private fun getOptimalBufferSize(rate: Int) = bufLut[App.audioBufferSize to rate]!! + private fun getOptimalBufferSize(rate: Float): Int { + val validRate = bufferRates.map { (rate - it) to it }.first { it.first >= 0 }.second + return bufLut[App.audioBufferSize to validRate]!! + } } private val q @@ -146,8 +156,8 @@ class AudioProcessBuf(val inputSamplingRate: Int, val audioReadFun: (FloatArray, private val finL = FloatArray(fetchSize + 2 * PADSIZE) private val finR = FloatArray(fetchSize + 2 * PADSIZE) - private val fmidL = FloatArray((fetchSize * 2 + 1.0).toInt()) - private val fmidR = FloatArray((fetchSize * 2 + 1.0).toInt()) + private val fmidL = FloatArray(fetchSize * 4) + private val fmidR = FloatArray(fetchSize * 4) private val foutL = FloatArray(internalBufferSize) // 640 for (44100, 48000), 512 for (48000, 48000) with BUFFER_SIZE = 512 * 4 private val foutR = FloatArray(internalBufferSize) // 640 for (44100, 48000), 512 for (48000, 48000) with BUFFER_SIZE = 512 * 4 private val readBufL = FloatArray(fetchSize) diff --git a/src/net/torvald/terrarum/audio/audiobank/MusicContainer.kt b/src/net/torvald/terrarum/audio/audiobank/MusicContainer.kt index 9dc550692..60ed56eb5 100644 --- a/src/net/torvald/terrarum/audio/audiobank/MusicContainer.kt +++ b/src/net/torvald/terrarum/audio/audiobank/MusicContainer.kt @@ -14,6 +14,7 @@ import net.torvald.reflection.forceInvoke import net.torvald.terrarum.App.printdbg import net.torvald.terrarum.audio.AudioBank import net.torvald.terrarum.audio.TerrarumAudioMixerTrack +import net.torvald.terrarum.audio.TerrarumAudioMixerTrack.Companion.SAMPLING_RATEF import net.torvald.terrarum.serialise.toUint import net.torvald.unsafe.UnsafeHelper import net.torvald.unsafe.UnsafePtr @@ -26,12 +27,36 @@ class MusicContainer( val file: File, val looping: Boolean = false, val toRAM: Boolean = false, + val samplingRateOverride: Float?, // this is FIXED sampling rate override var songFinishedHook: (AudioBank) -> Unit = {} ): AudioBank() { - override val samplingRate: Int + override val samplingRate: Float override val channels: Int val codec: String + // make Java code shorter + constructor( + name: String, + file: File, + looping: Boolean = false, + toRAM: Boolean = false, + songFinishedHook: (AudioBank) -> Unit = {} + ) : this(name, file, looping, toRAM, null, songFinishedHook) + // make Java code shorter + constructor( + name: String, + file: File, + looping: Boolean = false, + songFinishedHook: (AudioBank) -> Unit = {} + ) : this(name, file, looping, false, null, songFinishedHook) + // make Java code shorter + constructor( + name: String, + file: File, + songFinishedHook: (AudioBank) -> Unit = {} + ) : this(name, file, false, false, null, songFinishedHook) + + var samplesReadCount = 0L; internal set override val totalSizeInSamples: Long private val totalSizeInBytes: Long @@ -55,18 +80,18 @@ class MusicContainer( bytesPerSample = 2 * channels - samplingRate = when (gdxMusic) { + samplingRate = samplingRateOverride ?: when (gdxMusic) { is Wav.Music -> { val rate = gdxMusic.extortField("input")!!.sampleRate // App.printdbg(this, "music $name is WAV; rate = $rate") - rate + rate.toFloat() } is Ogg.Music -> { val rate = gdxMusic.extortField("input")!!.sampleRate // App.printdbg(this, "music $name is OGG; rate = $rate") - rate + rate.toFloat() } is Mp3.Music -> { val tempMusic = Gdx.audio.newMusic(Gdx.files.absolute(file.absolutePath)) @@ -81,11 +106,11 @@ class MusicContainer( // gdxMusic.reset() // App.printdbg(this, "music $name is MP3; rate = $rate") - rate + rate.toFloat() } else -> { // App.printdbg(this, "music $name is ${gdxMusic::class.qualifiedName}; rate = default") - TerrarumAudioMixerTrack.SAMPLING_RATE + TerrarumAudioMixerTrack.SAMPLING_RATEF } } @@ -315,7 +340,7 @@ class MusicContainer( } override fun makeCopy(): AudioBank { - val new = MusicContainer(name, file, looping, false, songFinishedHook) + val new = MusicContainer(name, file, looping, false, samplingRateOverride, songFinishedHook) synchronized(this) { if (this.toRAM) { diff --git a/src/net/torvald/terrarum/modulebasegame/audio/audiobank/AudioBankMusicBox.kt b/src/net/torvald/terrarum/modulebasegame/audio/audiobank/AudioBankMusicBox.kt index 005b35df1..534ab418e 100644 --- a/src/net/torvald/terrarum/modulebasegame/audio/audiobank/AudioBankMusicBox.kt +++ b/src/net/torvald/terrarum/modulebasegame/audio/audiobank/AudioBankMusicBox.kt @@ -19,7 +19,7 @@ class AudioBankMusicBox(override var songFinishedHook: (AudioBank) -> Unit = {}) } override val name = "spieluhr" - override val samplingRate = 48000 // 122880 // use 122880 to make each tick is 2048 samples + override val samplingRate = 48000f // 122880 // use 122880 to make each tick is 2048 samples override val channels = 1 private val getSample = // usage: getSample(noteNum 0..60) diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorLobbed.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorLobbed.kt index d3bcb2d5f..5e69424ed 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/ActorLobbed.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/ActorLobbed.kt @@ -16,29 +16,46 @@ import kotlin.math.log10 /** * Created by minjaesong on 2024-07-12. */ -open class ActorLobbed : ActorWithBody() +open class ActorLobbed(throwPitch: Float) : ActorWithBody() { + + protected constructor() : this(1f) + + @Transient private val whooshSound = MusicContainer( + "throw_low_short", ModMgr.getFile("basegame", "audio/effects/throwing/throw_low_short.wav"), + toRAM = true, + samplingRateOverride = 48000f * throwPitch.coerceIn(0.5f, 2f) + ) + + init { + renderOrder = RenderOrder.FRONT + physProp = PhysProperties.PHYSICS_OBJECT() + elasticity = 0.34 + } + + private var soundFired = false + + override fun updateImpl(delta: Float) { + super.updateImpl(delta) + if (!soundFired) { + soundFired = true + startAudio(whooshSound, 1.0) + } + } +} /** * Created by minjaesong on 2024-02-13. */ open class ActorPrimedBomb( + throwPitch: Float, @Transient private var explosionPower: Float = 1f, private var fuse: Second = 1f, @Transient private var dropProbNonOre: Float = 0.25f, @Transient private var dropProbOre: Float = 0.75f -) : ActorLobbed() { +) : ActorLobbed(throwPitch) { - init { - renderOrder = RenderOrder.MIDTOP - physProp = PhysProperties.PHYSICS_OBJECT() - elasticity = 0.34 - } - - protected constructor() : this(1f, 1f) { - renderOrder = RenderOrder.MIDTOP - physProp = PhysProperties.PHYSICS_OBJECT() - } + protected constructor() : this(1f, 1f, 1f) private var explosionCalled = false @@ -110,7 +127,10 @@ open class ActorPrimedBomb( /** * Created by minjaesong on 2024-02-14. */ -class ActorCherryBomb : ActorPrimedBomb(14f, 4.5f) { // 14 is the intended value; 32 is for testing +class ActorCherryBomb(throwPitch: Float) : ActorPrimedBomb(throwPitch, 14f, 4.5f) { // 14 is the intended value; 32 is for testing + + private constructor() : this(1f) + init { val itemImage = CommonResourcePool.getAsItemSheet("basegame.items").get(0,13) @@ -126,7 +146,10 @@ class ActorCherryBomb : ActorPrimedBomb(14f, 4.5f) { // 14 is the intended value /** * Created by minjaesong on 2024-07-12. */ -class ActorGlowOrb : ActorLobbed() { +class ActorGlowOrb(throwPitch: Float) : ActorLobbed(throwPitch) { + + private constructor() : this(1f) + val spawnTime = INGAME.world.worldTime.TIME_T init { diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/Cultivable.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/Cultivable.kt index 7c58a2bc2..bbcaad70e 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/Cultivable.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/Cultivable.kt @@ -51,6 +51,8 @@ open class Cultivable: FixtureBase { * Created by minjaesong on 2024-02-03. */ open class SaplingBase(val species: Int) : Cultivable(72000) { + private constructor() : this(0) + private val variant = (0..3).random() init { CommonResourcePool.addToLoadingList("basegame/sprites/saplings.tga") { diff --git a/src/net/torvald/terrarum/modulebasegame/gameactors/DroppedItem.kt b/src/net/torvald/terrarum/modulebasegame/gameactors/DroppedItem.kt index 8a3d48059..3276b8de2 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameactors/DroppedItem.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameactors/DroppedItem.kt @@ -16,13 +16,17 @@ import org.dyn4j.geometry.Vector2 /** * Created by minjaesong on 2016-03-15. */ -open class DroppedItem : ActorWithBody { +class DroppedItem : ActorWithBody { companion object { const val NO_PICKUP_TIME = 1f const val MERGER_RANGE = 8.0 * TILE_SIZED // the wanted distance, squared } + init { + renderOrder = RenderOrder.FRONT + } + var itemID: ItemID = ""; private set @Transient private var visualItemID = "" diff --git a/src/net/torvald/terrarum/modulebasegame/gameitems/ItemThrowable.kt b/src/net/torvald/terrarum/modulebasegame/gameitems/ItemThrowable.kt index f8fc98937..c2305f8e6 100644 --- a/src/net/torvald/terrarum/modulebasegame/gameitems/ItemThrowable.kt +++ b/src/net/torvald/terrarum/modulebasegame/gameitems/ItemThrowable.kt @@ -33,7 +33,10 @@ open class ItemThrowable(originalID: ItemID, private val throwableActorClassName override fun startPrimaryUse(actor: ActorWithBody, delta: Float): Long = mouseInInteractableRange(actor) { mx, my, mtx, mty -> val (throwPos, throwForce) = getThrowPosAndVector(actor) - val lobbed = Class.forName(throwableActorClassName).getDeclaredConstructor().newInstance() as ActorWithBody + val magnRel = throwForce.magnitude / actor.avStrength * 1000.0 + val pitch = (magnRel * 0.2).sqrt().toFloat() + + val lobbed = Class.forName(throwableActorClassName).getDeclaredConstructor(pitch.javaClass).newInstance(pitch) as ActorWithBody lobbed.setPositionFromCentrePoint(throwPos) lobbed.externalV.set(throwForce) setupLobbedActor(lobbed)