Skip to content

Commit 5fa099c

Browse files
committed
feat: ux
1 parent 729832e commit 5fa099c

File tree

5 files changed

+227
-5
lines changed

5 files changed

+227
-5
lines changed

src/main/kotlin/cc/modlabs/kpaper/extensions/UXExtension.kt

Lines changed: 133 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package cc.modlabs.kpaper.extensions
22

3+
import cc.modlabs.kpaper.visuals.effect.ParticleData
34
import dev.fruxz.ascend.extension.time.inWholeMinecraftTicks
45
import org.bukkit.Color
56
import org.bukkit.Location
@@ -14,6 +15,10 @@ import org.bukkit.potion.PotionEffect
1415
import org.bukkit.potion.PotionEffectType
1516
import kotlin.time.Duration
1617
import kotlin.time.Duration.Companion.seconds
18+
import com.destroystokyo.paper.ParticleBuilder
19+
import kotlinx.coroutines.GlobalScope
20+
import kotlinx.coroutines.delay
21+
import kotlinx.coroutines.launch
1722

1823

1924
/**
@@ -222,16 +227,53 @@ fun buildMelody(builder: MelodyBuilder.() -> Unit): Melody {
222227
}
223228

224229
// Melody and Beat classes
225-
class Melody(private val beats: List<Beat>) {
226-
fun play(player: Player) {
227-
beats.forEach { it.play(player) }
230+
class Melody(
231+
private var ticksPerBeat: Long = 10,
232+
private var ticksPerSound: Long = 0,
233+
var repetitions: Int = 0,
234+
private val beats: List<Beat>
235+
) {
236+
var delayPerBeat: Duration
237+
get() = ticksPerBeat.takeIf { it > 0 }?.minecraftTicks ?: Duration.ZERO
238+
set(value) {
239+
ticksPerBeat = value.inWholeMinecraftTicks
240+
}
241+
242+
var delayPerSound: Duration
243+
get() = ticksPerSound.takeIf { it > 0 }?.minecraftTicks ?: Duration.ZERO
244+
set(value) {
245+
ticksPerSound = value.inWholeMinecraftTicks
246+
}
247+
248+
fun play(player: Player) = GlobalScope.launch {
249+
repeat(1 + repetitions) {
250+
beats.forEach {
251+
it.play(player)
252+
delay(delayPerSound)
253+
}
254+
delay(delayPerBeat)
255+
}
256+
}
257+
258+
fun play(location: Location) = GlobalScope.launch {
259+
repeat(1 + repetitions) {
260+
beats.forEach {
261+
it.play(location)
262+
delay(delayPerSound)
263+
}
264+
delay(delayPerBeat)
265+
}
228266
}
229267
}
230268

231269
class Beat(private val sounds: List<SoundEffect>) {
232270
fun play(player: Player) {
233271
sounds.forEach { it.play(player) }
234272
}
273+
274+
fun play(location: Location) {
275+
sounds.forEach { it.play(location) }
276+
}
235277
}
236278

237279
data class SoundEffect(
@@ -244,16 +286,40 @@ data class SoundEffect(
244286
fun play(player: Player) {
245287
player.playSound(player.location, sound, volume, pitch)
246288
}
289+
290+
fun play(location: Location) {
291+
location.world.playSound(location, sound, volume, pitch)
292+
}
247293
}
248294

249295
class MelodyBuilder {
250296
private val beats = mutableListOf<Beat>()
297+
private var repetitions = 0
298+
private var delayPerBeat: Duration = Duration.ZERO
299+
private var delayPerSound: Duration = Duration.ZERO
251300

252301
fun beat(vararg sounds: SoundEffect) {
253302
beats.add(Beat(sounds.toList()))
254303
}
255304

256-
fun build(): Melody = Melody(beats)
305+
fun repeat(times: Int) {
306+
repetitions = times
307+
}
308+
309+
fun delayPerBeat(duration: Duration) {
310+
delayPerBeat = duration
311+
}
312+
313+
fun delayPerSound(duration: Duration) {
314+
delayPerSound = duration
315+
}
316+
317+
fun build(): Melody = Melody(
318+
ticksPerBeat = delayPerBeat.inWholeMinecraftTicks,
319+
ticksPerSound = delayPerSound.inWholeMinecraftTicks,
320+
repetitions = repetitions,
321+
beats = beats.toList()
322+
)
257323
}
258324

259325
// Utility function to create SoundEffect
@@ -275,4 +341,66 @@ fun PotionEffect(type: PotionEffectType, duration: Duration = 10.seconds, amplif
275341
PotionEffect(type, duration.inWholeMinecraftTicks.toInt(), amplifier, ambient, particles, icon)
276342

277343
fun buildPotionEffect(type: PotionEffectType, duration: Duration = 10.seconds, amplifier: Int = 0, ambient: Boolean = true, particles: Boolean = true, icon: Boolean = true, builder: PotionEffect.() -> Unit) =
278-
PotionEffect(type, duration, amplifier, ambient, particles, icon).apply(builder)
344+
PotionEffect(type, duration, amplifier, ambient, particles, icon).apply(builder)
345+
346+
347+
@Throws(IllegalStateException::class)
348+
fun ParticleBuilder.playParticleEffect(reach: Double = .0) {
349+
val location = location()
350+
val internalReceivers = receivers()?.toList() ?: location?.world?.players
351+
352+
if (location != null) {
353+
internalReceivers!!
354+
355+
if (reach > 0) {
356+
val participants = location.getNearbyPlayers(reach).filter { internalReceivers.contains(it) }
357+
val computedParticleBuilder = receivers(participants)
358+
359+
computedParticleBuilder.spawn()
360+
361+
} else
362+
spawn()
363+
364+
} else
365+
throw IllegalStateException("'location'[bukkit.Location] of ParticleBuilder cannot be null!")
366+
}
367+
368+
@Throws(IllegalStateException::class)
369+
fun ParticleBuilder.playParticleEffect(reach: Number = .0) =
370+
playParticleEffect(reach.toDouble())
371+
372+
fun ParticleBuilder.playParticleEffect() =
373+
playParticleEffect(.0)
374+
375+
fun ParticleBuilder.offset(offset: Number) = offset(offset.toDouble(), offset.toDouble(), offset.toDouble())
376+
377+
fun ParticleBuilder.offset(offsetX: Number, offsetZ: Number) = offset(offsetX.toDouble(), .0, offsetZ.toDouble())
378+
379+
fun ParticleBuilder.location(loc: Location) = location(loc)
380+
381+
fun particleOf(particle: Particle): ParticleData = ParticleData(particle)
382+
383+
fun buildParticle(particle: Particle, builder: ParticleData.() -> Unit) =
384+
ParticleData(particle).apply(builder)
385+
386+
fun ParticleBuilder.copy(
387+
particle: Particle = particle(),
388+
receivers: List<Player>? = receivers(),
389+
source: Player? = source(),
390+
location: Location? = location(),
391+
count: Int = count(),
392+
offsetX: Double = offsetX(),
393+
offsetY: Double = offsetY(),
394+
offsetZ: Double = offsetZ(),
395+
extra: Double = extra(),
396+
data: Any? = data(),
397+
force: Boolean = force(),
398+
) = ParticleBuilder(particle)
399+
.receivers(receivers)
400+
.source(source)
401+
.count(count)
402+
.offset(offsetX, offsetY, offsetZ)
403+
.extra(extra)
404+
.data(data)
405+
.force(force)
406+
.let { if (location != null) it.location(location) else it }
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package cc.modlabs.kpaper.visuals.effect
2+
3+
import org.bukkit.Location
4+
import org.bukkit.entity.Entity
5+
6+
data class ComplexParticleEffect(
7+
val effects: Set<ParticleEffect>,
8+
) : ParticleEffect {
9+
10+
override fun play(vararg entities: Entity?): Unit =
11+
effects.forEach { it.play(*entities) }
12+
13+
override fun play(vararg locations: Location?): Unit =
14+
effects.forEach { it.play(*locations) }
15+
16+
override fun play(): Unit =
17+
effects.forEach(ParticleEffect::play)
18+
19+
override fun play(locations: Set<Location>, entities: Set<Entity>): Unit =
20+
effects.forEach { effect -> effect.play(locations, entities) }
21+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package cc.modlabs.kpaper.visuals.effect
2+
3+
import cc.modlabs.kpaper.world.shape.CubicalShape
4+
import com.destroystokyo.paper.ParticleBuilder
5+
import dev.fruxz.ascend.extension.dump
6+
import org.bukkit.Location
7+
import org.bukkit.Particle
8+
import org.bukkit.entity.Entity
9+
import org.bukkit.entity.Player
10+
11+
data class ParticleData(
12+
val type: Particle,
13+
) : ParticleBuilder(type), ParticleEffect {
14+
15+
fun putData(data: Particle) =
16+
data(data)
17+
18+
fun offset(cube: CubicalShape) =
19+
offset(cube.length, cube.height, cube.depth)
20+
21+
fun edit(block: ParticleData.() -> Unit) = apply(block)
22+
23+
override fun play(): Unit = spawn().dump()
24+
25+
override fun play(vararg locations: Location?): Unit = locations.forEach { location ->
26+
if (location == null) return@forEach
27+
location(location).spawn()
28+
}
29+
30+
override fun play(vararg entities: Entity?): Unit =
31+
receivers(entities.filterIsInstance<Player>()).spawn().dump()
32+
33+
override fun play(locations: Set<Location>, entities: Set<Entity>) {
34+
val receivers = entities.filterIsInstance<Player>()
35+
36+
locations.forEach { location ->
37+
location(location).receivers(receivers).spawn()
38+
}
39+
40+
}
41+
42+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package cc.modlabs.kpaper.visuals.effect
2+
3+
import org.bukkit.Location
4+
import org.bukkit.entity.Entity
5+
6+
interface ParticleEffect {
7+
8+
fun play(locations: Set<Location>, entities: Set<Entity>)
9+
10+
fun play(location: Location, entities: Set<Entity>) =
11+
play(setOf(location), entities)
12+
13+
fun play(entity: Entity, locations: Set<Location>) =
14+
play(locations, setOf(entity))
15+
16+
fun play(location: Location, entity: Entity) =
17+
play(setOf(location), setOf(entity))
18+
19+
fun play(vararg entities: Entity?)
20+
21+
fun play(vararg locations: Location?)
22+
23+
fun play()
24+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package cc.modlabs.kpaper.world.shape
2+
3+
data class CubicalShape(
4+
val length: Double,
5+
val height: Double,
6+
val depth: Double,
7+
)

0 commit comments

Comments
 (0)