Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.

Commit d27c133

Browse files
authored
Merge pull request #149 from DockyardMC/feature/rolling-counter
Add `RollingCounter` util class
2 parents e29a91a + 4043151 commit d27c133

File tree

16 files changed

+409
-40
lines changed

16 files changed

+409
-40
lines changed

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/structuralSearch.xml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/kotlin/Main.kt

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import io.github.dockyardmc.DockyardServer
2+
import io.github.dockyardmc.commands.Commands
3+
import io.github.dockyardmc.commands.EnumArgument
4+
import io.github.dockyardmc.commands.IntArgument
25
import io.github.dockyardmc.events.Events
36
import io.github.dockyardmc.events.PlayerFlightToggleEvent
47
import io.github.dockyardmc.events.PlayerJoinEvent
58
import io.github.dockyardmc.events.ServerTickEvent
6-
import io.github.dockyardmc.extentions.broadcastMessage
9+
import io.github.dockyardmc.extentions.sendActionBar
10+
import io.github.dockyardmc.extentions.sendTitle
11+
import io.github.dockyardmc.maths.counter.RollingCounter
12+
import io.github.dockyardmc.maths.counter.RollingCounterInt
713
import io.github.dockyardmc.maths.randomFloat
814
import io.github.dockyardmc.maths.vectors.Vector3d
915
import io.github.dockyardmc.maths.vectors.Vector3f
@@ -12,15 +18,12 @@ import io.github.dockyardmc.player.Player
1218
import io.github.dockyardmc.player.PlayerManager
1319
import io.github.dockyardmc.player.systems.GameMode
1420
import io.github.dockyardmc.protocol.packets.play.serverbound.ServerboundClientInputPacket
15-
import io.github.dockyardmc.registry.Biomes
16-
import io.github.dockyardmc.registry.DimensionTypes
1721
import io.github.dockyardmc.registry.Particles
1822
import io.github.dockyardmc.registry.Sounds
1923
import io.github.dockyardmc.registry.registries.PotionEffectRegistry
2024
import io.github.dockyardmc.sounds.playSound
2125
import io.github.dockyardmc.utils.DebugSidebar
22-
import io.github.dockyardmc.world.WorldManager
23-
import io.github.dockyardmc.world.generators.VoidWorldGenerator
26+
import kotlin.time.Duration.Companion.seconds
2427

2528
fun suggestPotionEffects(player: Player): List<String> {
2629
return PotionEffectRegistry.potionEffects.keys.toList()
@@ -42,9 +45,31 @@ fun main() {
4245
player.canFly.value = true
4346
}
4447

45-
Events.on<ServerTickEvent> { event ->
46-
PlayerManager.players.forEach { player ->
47-
player.sendActionBar("<yellow>Holding: ${player.heldInputs}")
48+
val rollingCounter = RollingCounterInt(DockyardServer.scheduler)
49+
rollingCounter.isRollingProportional = false
50+
rollingCounter.rollingDuration = 5.seconds
51+
rollingCounter.rollingEasing = RollingCounter.Easing.IN_EXPO
52+
rollingCounter.rollDispatcher.subscribe { int ->
53+
PlayerManager.players.sendTitle("<lime>${int}", "", 0, 9999, 0)
54+
}
55+
56+
Events.on<ServerTickEvent> { _ ->
57+
PlayerManager.players.sendActionBar("<yellow>Counter is at: <lime><bold>${rollingCounter.animatedDisplayValue}")
58+
}
59+
60+
Commands.add("/counter") {
61+
addSubcommand("set") {
62+
addArgument("number", IntArgument())
63+
addArgument("seconds", IntArgument())
64+
addArgument("easing", EnumArgument(RollingCounter.Easing::class))
65+
execute { _ ->
66+
val number = getArgument<Int>("number")
67+
val time = getArgument<Int>("seconds")
68+
val easing = getEnumArgument<RollingCounter.Easing>("easing")
69+
rollingCounter.rollingEasing = easing
70+
rollingCounter.rollingDuration = time.seconds
71+
rollingCounter.value.value = number
72+
}
4873
}
4974
}
5075

src/main/kotlin/io/github/dockyardmc/maths/MathUtils.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,8 @@ fun chunkInSpiral(id: Int, xOffset: Int = 0, zOffset: Int = 0): Pair<Int, Int> {
139139
3 -> -radius + xOffset to radius - a % en + zOffset
140140
else -> 0 to 0
141141
}
142-
}
142+
}
143+
144+
fun sin(float: Float): Float {
145+
return sin(float.toDouble()).toFloat()
146+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.github.dockyardmc.maths.counter
2+
3+
import io.github.dockyardmc.DockyardServer
4+
import io.github.dockyardmc.scheduler.Scheduler
5+
import io.github.dockyardmc.scheduler.SchedulerTask
6+
import io.github.dockyardmc.scheduler.runnables.ticks
7+
import io.github.dockyardmc.utils.Disposable
8+
import kotlin.time.Duration
9+
10+
class AnimationProvider(val scheduler: Scheduler) : Disposable {
11+
private var running = false
12+
private var schedulerTask: SchedulerTask? = null
13+
14+
fun start(duration: Duration, easingFunction: RollingCounter.Easing, onUpdate: (Float) -> Unit) {
15+
if (running) {
16+
stop()
17+
}
18+
19+
running = true
20+
val startTime = System.currentTimeMillis()
21+
22+
schedulerTask = scheduler.runRepeating(1.ticks) { task ->
23+
24+
if (!running) {
25+
stop()
26+
task.cancel()
27+
return@runRepeating
28+
}
29+
30+
val elapsed = System.currentTimeMillis() - startTime
31+
val rawProgress = (elapsed.toFloat() / duration.inWholeMilliseconds).coerceIn(0f, 1f)
32+
val easedProgress = easingFunction.transform(rawProgress)
33+
34+
onUpdate(easedProgress)
35+
36+
if (rawProgress >= 1f) {
37+
stop()
38+
task.cancel()
39+
return@runRepeating
40+
}
41+
}
42+
}
43+
44+
fun stop() {
45+
schedulerTask?.cancel()
46+
schedulerTask = null
47+
running = false
48+
}
49+
50+
override fun dispose() {
51+
stop()
52+
}
53+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.github.dockyardmc.maths.counter
2+
3+
import cz.lukynka.bindables.Bindable
4+
import io.github.dockyardmc.maths.sin
5+
import io.github.dockyardmc.scheduler.Scheduler
6+
import kotlin.math.PI
7+
import kotlin.math.cos
8+
import kotlin.math.pow
9+
import kotlin.math.sqrt
10+
import kotlin.time.Duration
11+
12+
abstract class RollingCounter<T>(val scheduler: Scheduler) {
13+
abstract val value: Bindable<T>
14+
15+
abstract var animatedDisplayValue: T
16+
17+
var isRollingProportional: Boolean = false
18+
19+
var rollingDuration: Duration = Duration.ZERO
20+
21+
var rollingEasing: Easing = Easing.OUT_QUAD
22+
23+
protected val animationProvider: AnimationProvider = AnimationProvider(scheduler)
24+
25+
protected abstract fun getProportionalRollDuration(current: T, new: T): Duration
26+
27+
protected abstract fun interpolate(start: T, end: T, progress: Float): T
28+
29+
enum class Easing(val transform: (t: Float) -> Float) {
30+
IN_SINE({ value -> 1 - cos(value * PI.toFloat() / 2f) }),
31+
OUT_SINE({ value -> sin(value * PI.toFloat() / 2f) }),
32+
IN_QUAD({ value -> value * value }),
33+
OUT_QUAD({ value -> 1 - (1 - value) * (1 - value) }),
34+
IN_CUBIC({ value -> value * value * value }),
35+
OUT_CUBIC({ value -> 1 - (1 - value).pow(3f) }),
36+
IN_QUART({ value -> value * value * value * value }),
37+
OUT_QUART({ value -> 1 - (1 - value).pow(4) }),
38+
IN_EXPO({ value -> if (value == 0f) 0f else 2f.pow(10 * value - 10) }),
39+
OUT_EXPO({ value -> if (value == 1f) 1f else 1f - 2f.pow(-10f * value) }),
40+
IN_CIRC({ value -> sqrt(1f - value.pow(2f)) }),
41+
OUT_CIRC({ value -> sqrt(1f - (value - 1f).pow(2)) }),
42+
IN_ELASTIC({ value ->
43+
when (value) {
44+
0f -> 0f
45+
1f -> 1f
46+
else -> {
47+
val p = 0.3f
48+
val a = 1f
49+
val s = p / 4f
50+
-(a * 2f.pow(10 * (value - 1))) * sin((value - 1 - s) * (2 * PI.toFloat()) / p)
51+
}
52+
}
53+
}),
54+
OUT_ELASTIC({ value ->
55+
when (value) {
56+
0f -> 0f
57+
1f -> 1f
58+
else -> {
59+
val p = 0.3f
60+
val a = 1f
61+
val s = p / 4f
62+
a * 2f.pow(-10 * value) * sin((value - s) * (2 * PI.toFloat()) / p) + 1
63+
}
64+
}
65+
}),
66+
IN_BOUNCE({ value ->
67+
val n1 = 7.5625f
68+
val d1 = 2.75f
69+
val v = 1 - value
70+
if (v < 1 / d1) {
71+
n1 * v * v
72+
} else if (v < 2 / d1) {
73+
n1 * (v - 1.5f / d1) * (v - 1.5f / d1) + 0.75f
74+
} else if (v < 2.5 / d1) {
75+
n1 * (v - 2.25f / d1) * (v - 2.25f / d1) + 0.9375f
76+
} else {
77+
n1 * (v - 2.625f / d1) * (v - 2.625f / d1) + 0.984375f
78+
}
79+
}),
80+
OUT_BOUNCE({ value ->
81+
val n1 = 7.5625f
82+
val d1 = 2.75f
83+
if (value < 1 / d1) {
84+
n1 * value * value
85+
} else if (value < 2 / d1) {
86+
n1 * (value - 1.5f / d1) * (value - 1.5f / d1) + 0.75f
87+
} else if (value < 2.5 / d1) {
88+
n1 * (value - 2.25f / d1) * (value - 2.25f / d1) + 0.9375f
89+
} else {
90+
n1 * (value - 2.625f / d1) * (value - 2.625f / d1) + 0.984375f
91+
}
92+
});
93+
}
94+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.github.dockyardmc.maths.counter
2+
3+
import cz.lukynka.bindables.Bindable
4+
import cz.lukynka.bindables.BindableDispatcher
5+
import io.github.dockyardmc.scheduler.Scheduler
6+
import kotlin.math.abs
7+
import kotlin.time.Duration
8+
9+
class RollingCounterDouble(scheduler: Scheduler) : RollingCounter<Double>(scheduler) {
10+
11+
override val value: Bindable<Double> = Bindable<Double>(0.0)
12+
13+
override var animatedDisplayValue: Double = value.value
14+
15+
override fun getProportionalRollDuration(current: Double, new: Double): Duration {
16+
if (!isRollingProportional) return rollingDuration
17+
18+
val difference = abs(new - current)
19+
return rollingDuration * difference
20+
}
21+
22+
val rollDispatcher: BindableDispatcher<Double> = BindableDispatcher()
23+
24+
init {
25+
value.valueChanged { event ->
26+
if(event.newValue == event.oldValue) return@valueChanged
27+
animationProvider.stop()
28+
29+
animationProvider.start(getProportionalRollDuration(event.oldValue, event.newValue), rollingEasing) { progress ->
30+
val new = interpolate(event.oldValue, event.newValue, progress)
31+
if (animatedDisplayValue != new) {
32+
animatedDisplayValue = new
33+
rollDispatcher.dispatch(animatedDisplayValue)
34+
}
35+
}
36+
}
37+
}
38+
39+
override fun interpolate(start: Double, end: Double, progress: Float): Double {
40+
return start + (end - start) * progress
41+
}
42+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package io.github.dockyardmc.maths.counter
2+
3+
import cz.lukynka.bindables.Bindable
4+
import cz.lukynka.bindables.BindableDispatcher
5+
import io.github.dockyardmc.scheduler.Scheduler
6+
import kotlin.math.abs
7+
import kotlin.math.roundToInt
8+
import kotlin.time.Duration
9+
10+
class RollingCounterFloat(scheduler: Scheduler) : RollingCounter<Float>(scheduler) {
11+
12+
override val value: Bindable<Float> = Bindable<Float>(0f)
13+
14+
override var animatedDisplayValue: Float = value.value
15+
16+
override fun getProportionalRollDuration(current: Float, new: Float): Duration {
17+
if (!isRollingProportional) return rollingDuration
18+
19+
val difference = abs(new - current)
20+
return rollingDuration * difference.toDouble()
21+
}
22+
23+
val rollDispatcher: BindableDispatcher<Float> = BindableDispatcher()
24+
25+
init {
26+
value.valueChanged { event ->
27+
if(event.newValue == event.oldValue) return@valueChanged
28+
animationProvider.stop()
29+
30+
animationProvider.start(getProportionalRollDuration(event.oldValue, event.newValue), rollingEasing) { progress ->
31+
val new = interpolate(event.oldValue, event.newValue, progress)
32+
if (animatedDisplayValue != new) {
33+
animatedDisplayValue = new
34+
rollDispatcher.dispatch(animatedDisplayValue)
35+
}
36+
}
37+
}
38+
}
39+
40+
override fun interpolate(start: Float, end: Float, progress: Float): Float {
41+
return start + (end - start) * progress
42+
}
43+
}

0 commit comments

Comments
 (0)