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