feat: Add /firm timer command
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package moe.nea.firmament.mixins;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.fabricmc.fabric.impl.command.client.ClientCommandInternals;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(ClientCommandInternals.class)
|
||||
public class AlwaysDisplayFirmamentClientCommandErrors {
|
||||
@ModifyExpressionValue(method = "executeCommand", at = @At(value = "INVOKE", target = "Lnet/fabricmc/fabric/impl/command/client/ClientCommandInternals;isIgnoredException(Lcom/mojang/brigadier/exceptions/CommandExceptionType;)Z"))
|
||||
private static boolean markFirmamentExceptionsAsNotIgnores(boolean original, @Local(argsOnly = true) String command) {
|
||||
if (command.startsWith("firm ") || command.equals("firm") || command.startsWith("firmament ") || command.equals("firmament")) {
|
||||
return false;
|
||||
}
|
||||
return original;
|
||||
}
|
||||
}
|
||||
75
src/main/kotlin/commands/Duration.kt
Normal file
75
src/main/kotlin/commands/Duration.kt
Normal file
@@ -0,0 +1,75 @@
|
||||
package moe.nea.firmament.commands
|
||||
|
||||
import com.mojang.brigadier.StringReader
|
||||
import com.mojang.brigadier.arguments.ArgumentType
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType
|
||||
import com.mojang.brigadier.suggestion.Suggestions
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.function.Function
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
import moe.nea.firmament.util.tr
|
||||
|
||||
object DurationArgumentType : ArgumentType<Duration> {
|
||||
val unknownTimeCode = DynamicCommandExceptionType { timeCode ->
|
||||
tr("firmament.command-argument.duration.error",
|
||||
"Unknown time code '$timeCode'")
|
||||
}
|
||||
|
||||
override fun parse(reader: StringReader): Duration {
|
||||
val start = reader.cursor
|
||||
val string = reader.readUnquotedString()
|
||||
val matcher = regex.matcher(string)
|
||||
var s = 0
|
||||
var time = 0.seconds
|
||||
fun createError(till: Int) {
|
||||
throw unknownTimeCode.createWithContext(
|
||||
reader.also { it.cursor = start + s },
|
||||
string.substring(s, till))
|
||||
}
|
||||
|
||||
while (matcher.find()) {
|
||||
if (matcher.start() != s) {
|
||||
createError(matcher.start())
|
||||
}
|
||||
s = matcher.end()
|
||||
val amount = matcher.group("count").toDouble()
|
||||
val what = timeSuffixes[matcher.group("what").single()]!!
|
||||
time += amount.toDuration(what)
|
||||
}
|
||||
if (string.length != s) {
|
||||
createError(string.length)
|
||||
}
|
||||
return time
|
||||
}
|
||||
|
||||
|
||||
override fun <S : Any?> listSuggestions(
|
||||
context: CommandContext<S>,
|
||||
builder: SuggestionsBuilder
|
||||
): CompletableFuture<Suggestions> {
|
||||
val remaining = builder.remainingLowerCase.substringBefore(' ')
|
||||
if (remaining.isEmpty()) return super.listSuggestions(context, builder)
|
||||
if (remaining.last().isDigit()) {
|
||||
for (timeSuffix in timeSuffixes.keys) {
|
||||
builder.suggest(remaining + timeSuffix)
|
||||
}
|
||||
}
|
||||
return builder.buildFuture()
|
||||
}
|
||||
|
||||
val timeSuffixes = mapOf(
|
||||
'm' to DurationUnit.MINUTES,
|
||||
's' to DurationUnit.SECONDS,
|
||||
'h' to DurationUnit.HOURS,
|
||||
)
|
||||
val regex = "(?<count>[0-9]+)(?<what>[${timeSuffixes.keys.joinToString("")}])".toPattern()
|
||||
|
||||
override fun getExamples(): Collection<String> {
|
||||
return listOf("3m", "20s", "1h45m")
|
||||
}
|
||||
}
|
||||
124
src/main/kotlin/features/misc/TimerFeature.kt
Normal file
124
src/main/kotlin/features/misc/TimerFeature.kt
Normal file
@@ -0,0 +1,124 @@
|
||||
package moe.nea.firmament.features.misc
|
||||
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import moe.nea.firmament.Firmament
|
||||
import moe.nea.firmament.annotations.Subscribe
|
||||
import moe.nea.firmament.commands.DurationArgumentType
|
||||
import moe.nea.firmament.commands.RestArgumentType
|
||||
import moe.nea.firmament.commands.get
|
||||
import moe.nea.firmament.commands.thenArgument
|
||||
import moe.nea.firmament.commands.thenExecute
|
||||
import moe.nea.firmament.events.CommandEvent
|
||||
import moe.nea.firmament.events.TickEvent
|
||||
import moe.nea.firmament.util.CommonSoundEffects
|
||||
import moe.nea.firmament.util.FirmFormatters
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.MinecraftDispatcher
|
||||
import moe.nea.firmament.util.TimeMark
|
||||
import moe.nea.firmament.util.clickCommand
|
||||
import moe.nea.firmament.util.lime
|
||||
import moe.nea.firmament.util.red
|
||||
import moe.nea.firmament.util.tr
|
||||
import moe.nea.firmament.util.yellow
|
||||
|
||||
object TimerFeature {
|
||||
data class Timer(
|
||||
val start: TimeMark,
|
||||
val duration: Duration,
|
||||
val message: String,
|
||||
val timerId: Int,
|
||||
) {
|
||||
fun timeLeft() = (duration - start.passedTime()).coerceAtLeast(0.seconds)
|
||||
fun isDone() = start.passedTime() >= duration
|
||||
}
|
||||
|
||||
// Theoretically for optimal performance this could be a treeset keyed to the end time
|
||||
val timers = mutableListOf<Timer>()
|
||||
|
||||
@Subscribe
|
||||
fun tick(event: TickEvent) {
|
||||
timers.removeAll {
|
||||
if (it.isDone()) {
|
||||
MC.sendChat(tr("firmament.timer.finished",
|
||||
"The timer you set ${FirmFormatters.formatTimespan(it.duration)} ago just went off: ${it.message}")
|
||||
.yellow())
|
||||
Firmament.coroutineScope.launch {
|
||||
withContext(MinecraftDispatcher) {
|
||||
repeat(5) {
|
||||
CommonSoundEffects.playSuccess()
|
||||
delay(0.2.seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startTimer(duration: Duration, message: String) {
|
||||
val timerId = createTimerId++
|
||||
timers.add(Timer(TimeMark.now(), duration, message, timerId))
|
||||
MC.sendChat(
|
||||
tr("firmament.timer.start",
|
||||
"Timer started for $message in ${FirmFormatters.formatTimespan(duration)}.").lime()
|
||||
.append(" ")
|
||||
.append(
|
||||
tr("firmament.timer.cancelbutton",
|
||||
"Click here to cancel the timer."
|
||||
).clickCommand("/firm timer clear $timerId").red()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun clearTimer(timerId: Int) {
|
||||
val timer = timers.indexOfFirst { it.timerId == timerId }
|
||||
if (timer < 0) {
|
||||
MC.sendChat(tr("firmament.timer.cancel.fail",
|
||||
"Could not cancel that timer. Maybe it was already cancelled?").red())
|
||||
} else {
|
||||
val timerData = timers[timer]
|
||||
timers.removeAt(timer)
|
||||
MC.sendChat(tr("firmament.timer.cancel.done",
|
||||
"Cancelled timer ${timerData.message}. It would have been done in ${
|
||||
FirmFormatters.formatTimespan(timerData.timeLeft())
|
||||
}.").lime())
|
||||
}
|
||||
}
|
||||
|
||||
var createTimerId = 0
|
||||
|
||||
@Subscribe
|
||||
fun onCommands(event: CommandEvent.SubCommand) {
|
||||
event.subcommand("cleartimer") {
|
||||
thenArgument("timerId", IntegerArgumentType.integer(0)) { timerId ->
|
||||
thenExecute {
|
||||
clearTimer(this[timerId])
|
||||
}
|
||||
}
|
||||
thenExecute {
|
||||
timers.map { it.timerId }.forEach {
|
||||
clearTimer(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
event.subcommand("timer") {
|
||||
thenArgument("time", DurationArgumentType) { duration ->
|
||||
thenExecute {
|
||||
startTimer(this[duration], "no message")
|
||||
}
|
||||
thenArgument("message", RestArgumentType) { message ->
|
||||
thenExecute {
|
||||
startTimer(this[duration], this[message])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,8 +142,7 @@ fun MutableText.bold(): MutableText = styled { it.withBold(true) }
|
||||
fun MutableText.clickCommand(command: String): MutableText {
|
||||
require(command.startsWith("/"))
|
||||
return this.styled {
|
||||
it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND,
|
||||
"/firm disablereiwarning"))
|
||||
it.withClickEvent(ClickEvent(ClickEvent.Action.RUN_COMMAND, command))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user