mirror of
https://github.com/RDCN-Community-Developers/rdkt.git
synced 2026-09-04 17:25:33 +08:00
Added AddOneshotBeatEvent.
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
package cn.rdlevel.rdkt.core.data
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmStatic
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the freeze or burn modifier applied to a oneshot pulse.
|
||||||
|
* Used in [cn.rdlevel.rdkt.core.events.AddOneshotBeatEvent].
|
||||||
|
*/
|
||||||
|
public sealed class OneshotFreezeBurn(public val type: String) {
|
||||||
|
/**
|
||||||
|
* Neither freeze nor burn modifier is applied to the oneshot pulse.
|
||||||
|
*/
|
||||||
|
public data object None : OneshotFreezeBurn("None")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The oneshot pulse is frozen and the actual hit point is delayed for a certain duration.
|
||||||
|
*
|
||||||
|
* @param delay The duration of the delay in beats.
|
||||||
|
*/
|
||||||
|
public data class Freezeshot(val delay: Double) : OneshotFreezeBurn("Freezeshot")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The oneshot pulse is burned and the actual hit point is advanced by a certain duration.
|
||||||
|
*/
|
||||||
|
public data object Burnshot : OneshotFreezeBurn("Burnshot")
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
/**
|
||||||
|
* Returns an instance of [None], indicating that neither freeze nor burn modifier is applied to the oneshot pulse.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun none(): None = None
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of [Freezeshot] with the specified delay, indicating that the oneshot pulse is frozen and the actual hit point is delayed for the given duration.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun freezeshot(delay: Double): Freezeshot = Freezeshot(delay)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of [Burnshot], indicating that the oneshot pulse is burned and the actual hit point is advanced by a certain duration.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun burnshot(): Burnshot = Burnshot
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package cn.rdlevel.rdkt.core.data
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.jvm.JvmOverloads
|
||||||
|
import kotlin.jvm.JvmStatic
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the hold settings of a oneshot pulse.
|
||||||
|
* Used in [cn.rdlevel.rdkt.core.events.AddOneshotBeatEvent].
|
||||||
|
*/
|
||||||
|
public sealed class OneshotHold {
|
||||||
|
/**
|
||||||
|
* The oneshot pulse is not holdshot.
|
||||||
|
*/
|
||||||
|
public data object Off : OneshotHold()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The oneshot pulse is holdshot.
|
||||||
|
*
|
||||||
|
* @property cueType The type of the holdshot cue.
|
||||||
|
*/
|
||||||
|
public data class On(var cueType: CueType = CueType.AUTO) : OneshotHold()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents when the holdshot cue is cued.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
public enum class CueType {
|
||||||
|
/**
|
||||||
|
* The time of holdshot cue is automatically determined.
|
||||||
|
*/
|
||||||
|
@SerialName("Auto")
|
||||||
|
AUTO,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cue is cued on the end of the previous oneshot.
|
||||||
|
*/
|
||||||
|
@SerialName("Early")
|
||||||
|
EARLY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cue is cued on the start of the pulse of the holdshot.
|
||||||
|
*/
|
||||||
|
@SerialName("Late")
|
||||||
|
LATE,
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
/**
|
||||||
|
* Returns an instance of [Off], indicating that the oneshot pulse is not holdshot.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun off(): Off = Off
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of [On] with the specified cue type, indicating that the oneshot pulse is holdshot with the given cue type.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
public fun on(cueType: CueType = CueType.AUTO): On = On(cueType)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package cn.rdlevel.rdkt.core.data
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmOverloads
|
||||||
|
import kotlin.jvm.JvmStatic
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a oneshot pulse with its settings.
|
||||||
|
* Used in [cn.rdlevel.rdkt.core.events.AddOneshotBeatEvent].
|
||||||
|
*/
|
||||||
|
public sealed class OneshotPulse(
|
||||||
|
public val pulseType: String,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* The number of subdivisions for the pulse. Must be between 1 and 10.
|
||||||
|
* For normal waves this is defaulted to 1.
|
||||||
|
*/
|
||||||
|
public abstract val subdivisions: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent a oneshot that can have subdivisions and sound settings.
|
||||||
|
*
|
||||||
|
* @property sound Whether the pulse should have special sounds. Default is true.
|
||||||
|
*/
|
||||||
|
public sealed class OneshotSubdivisionPulse(
|
||||||
|
pulseType: String,
|
||||||
|
public var sound: Boolean = true
|
||||||
|
) : OneshotPulse(pulseType)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a simple oneshot pulse without subdivisions or sound settings.
|
||||||
|
*/
|
||||||
|
public object Wave : OneshotPulse("Wave") {
|
||||||
|
override val subdivisions: Int
|
||||||
|
get() = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a heart-shaped oneshot pulse without subdivisions or sound settings.
|
||||||
|
*/
|
||||||
|
public object Heart : OneshotPulse("Heart") {
|
||||||
|
override val subdivisions: Int
|
||||||
|
get() = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a oneshot pulse with a single subdivision (squareshot) and sound settings.
|
||||||
|
*/
|
||||||
|
public class Square(sound: Boolean) : OneshotSubdivisionPulse("Square", sound) {
|
||||||
|
override val subdivisions: Int
|
||||||
|
get() = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a oneshot pulse with multiple subdivisions (triangleshot) and sound settings.
|
||||||
|
*/
|
||||||
|
public class Triangle(subdivisions: Int, sound: Boolean) : OneshotSubdivisionPulse("Triangle", sound) {
|
||||||
|
override var subdivisions: Int = subdivisions
|
||||||
|
set(value) {
|
||||||
|
require(value in 1..10) { "Subdivisions must be between 1 and 10." }
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.subdivisions = subdivisions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
/**
|
||||||
|
* Returns a [Wave] oneshot pulse, which has no subdivisions or sound settings.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun wave(): Wave = Wave
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [Heart] oneshot pulse, which has no subdivisions or sound settings.
|
||||||
|
*/
|
||||||
|
public fun heart(): Heart = Heart
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns either a [Square] or a [Triangle] based on the number of subdivisions.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
|
public fun subdivision(subdivisions: Int, sound: Boolean = true): OneshotSubdivisionPulse =
|
||||||
|
when (subdivisions) {
|
||||||
|
1 -> Square(sound)
|
||||||
|
in 2..10 -> Triangle(subdivisions, sound)
|
||||||
|
else -> throw IllegalArgumentException("Subdivisions must be between 1 and 10.")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun from(type: String, subdivisions: Int, sound: Boolean): OneshotPulse = when (type) {
|
||||||
|
"Wave" -> wave()
|
||||||
|
"Square" -> subdivision(1, sound)
|
||||||
|
"Triangle" -> subdivision(subdivisions, sound)
|
||||||
|
else -> throw IllegalArgumentException("Unknown pulse type: $type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
@file:OptIn(RDKTInternalAPI::class)
|
||||||
|
|
||||||
|
package cn.rdlevel.rdkt.core.events
|
||||||
|
|
||||||
|
|
||||||
|
import cn.rdlevel.rdkt.core.annotations.RDKTInternalAPI
|
||||||
|
import cn.rdlevel.rdkt.core.data.OneshotFreezeBurn
|
||||||
|
import cn.rdlevel.rdkt.core.data.OneshotHold
|
||||||
|
import cn.rdlevel.rdkt.core.data.OneshotPulse
|
||||||
|
import cn.rdlevel.rdkt.core.serialization.TransformSerializer
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.jvm.JvmStatic
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a oneshot pulse to the specific row.
|
||||||
|
*
|
||||||
|
* @property pulse The type of the oneshot pulse to be added.
|
||||||
|
* @property freezeBurnMode The freeze or burn modifier applied to the oneshot pulse.
|
||||||
|
* @property hold The hold behavior of the oneshot pulse.
|
||||||
|
* @property skip Whether the oneshot pulse is a skipshot.
|
||||||
|
*/
|
||||||
|
@Serializable(AddOneshotBeatEvent.Serializer::class)
|
||||||
|
@SerialName("AddOneshotBeat")
|
||||||
|
public data class AddOneshotBeatEvent(
|
||||||
|
var pulse: OneshotPulse = OneshotPulse.Wave,
|
||||||
|
var freezeBurnMode: OneshotFreezeBurn = OneshotFreezeBurn.None,
|
||||||
|
var hold: OneshotHold = OneshotHold.Off,
|
||||||
|
var skip: Boolean = false,
|
||||||
|
) : RowEvent() {
|
||||||
|
/**
|
||||||
|
* The time between the pulse appears and the actual hit point in beats.
|
||||||
|
* Must be non-negative.
|
||||||
|
*/
|
||||||
|
var tick: Double = 1.0
|
||||||
|
set(value) {
|
||||||
|
require(value >= 0) { "Tick must be non-negative." }
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interval between every two hits with normal looped oneshots in beats.
|
||||||
|
* Must be non-negative.
|
||||||
|
* This value is only used if this is a skipshot, freezeshot, burnshot, holdshot, or [loops] is positive.
|
||||||
|
*/
|
||||||
|
var interval: Double = 2.0
|
||||||
|
set(value) {
|
||||||
|
require(value >= 0) { "Interval must be non-negative." }
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many repetitions of the oneshot pattern will create. Must be non-negative.
|
||||||
|
* Setting this to 0 means the oneshot will only occur once.
|
||||||
|
*/
|
||||||
|
var loops: Int = 0
|
||||||
|
set(value) {
|
||||||
|
require(value >= 0) { "Loops must be non-negative." }
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Serializer :
|
||||||
|
TransformSerializer<AddOneshotBeatEvent, Serializer.Data>("AddOneshotBeat", Data.serializer()) {
|
||||||
|
override fun toData(value: AddOneshotBeatEvent): Data {
|
||||||
|
return Data.fromBase(value).apply {
|
||||||
|
pulseType = value.pulse.pulseType
|
||||||
|
freezeBurnMode = value.freezeBurnMode.type
|
||||||
|
interval = value.interval
|
||||||
|
tick = value.tick
|
||||||
|
delay = (value.freezeBurnMode as? OneshotFreezeBurn.Freezeshot)?.delay
|
||||||
|
loops = value.loops
|
||||||
|
subdivisions = value.pulse.subdivisions
|
||||||
|
subdivSound = (value.pulse as? OneshotPulse.OneshotSubdivisionPulse)?.sound
|
||||||
|
skipshot = value.skip
|
||||||
|
hold = value.hold is OneshotHold.On
|
||||||
|
holdCue = (value.hold as? OneshotHold.On)?.cueType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fromData(data: Data): AddOneshotBeatEvent {
|
||||||
|
val pulse = OneshotPulse.from(data.pulseType, data.subdivisions, data.subdivSound ?: true)
|
||||||
|
val freezeBurnMode = when (data.freezeBurnMode) {
|
||||||
|
"None", null -> OneshotFreezeBurn.None
|
||||||
|
"Freezeshot" -> OneshotFreezeBurn.Freezeshot(
|
||||||
|
data.delay ?: error("Delay must be provided for Freezeshot mode.")
|
||||||
|
)
|
||||||
|
|
||||||
|
"Burnshot" -> OneshotFreezeBurn.Burnshot
|
||||||
|
else -> throw IllegalArgumentException("Unknown freeze/burn mode: ${data.freezeBurnMode}")
|
||||||
|
}
|
||||||
|
val hold = if (data.hold == true) {
|
||||||
|
OneshotHold.On(data.holdCue ?: OneshotHold.CueType.AUTO)
|
||||||
|
} else {
|
||||||
|
OneshotHold.Off
|
||||||
|
}
|
||||||
|
return AddOneshotBeatEvent(
|
||||||
|
pulse = pulse,
|
||||||
|
freezeBurnMode = freezeBurnMode,
|
||||||
|
hold = hold,
|
||||||
|
skip = data.skipshot ?: false,
|
||||||
|
).apply {
|
||||||
|
copyBaseFrom(data)
|
||||||
|
this.interval = data.interval
|
||||||
|
this.tick = data.tick
|
||||||
|
this.loops = data.loops
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@RDKTInternalAPI
|
||||||
|
public data class Data(
|
||||||
|
var pulseType: String = "",
|
||||||
|
var freezeBurnMode: String? = null,
|
||||||
|
var interval: Double = 2.0,
|
||||||
|
var tick: Double = 1.0,
|
||||||
|
var delay: Double? = 0.5,
|
||||||
|
var loops: Int = 0,
|
||||||
|
var subdivisions: Int = 0,
|
||||||
|
var subdivSound: Boolean? = true,
|
||||||
|
var skipshot: Boolean? = false,
|
||||||
|
var hold: Boolean? = false,
|
||||||
|
var holdCue: OneshotHold.CueType? = OneshotHold.CueType.AUTO,
|
||||||
|
) : RowEvent() {
|
||||||
|
public companion object {
|
||||||
|
public fun fromBase(other: RowEvent): Data {
|
||||||
|
return Data().apply { copyBaseFrom(other) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
/**
|
||||||
|
* Creates a new [AddOneshotBeatEvent] using the provided block to configure its properties.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
public fun of(block: AddOneshotBeatEvent.() -> Unit): AddOneshotBeatEvent {
|
||||||
|
return AddOneshotBeatEvent().apply(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user