Skip to content

Commit

Permalink
refactor: Refactor teleport request handler and tick loop
Browse files Browse the repository at this point in the history
  • Loading branch information
half-nothing committed Sep 22, 2024
1 parent a663377 commit c14dae7
Show file tree
Hide file tree
Showing 28 changed files with 416 additions and 260 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ group = mavenGroup
repositories {
maven("https://maven.architectury.dev/")
maven("https://maven.nucleoid.xyz")
mavenCentral()
mavenLocal()
mavenCentral()
}

dependencies {
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/net/superricky/tpaplusplus/TpaPlusPlus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dev.architectury.event.events.common.PlayerEvent
import dev.architectury.event.events.common.TickEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
Expand Down Expand Up @@ -106,5 +107,6 @@ object TpaPlusPlus : ModInitializer, CoroutineScope {
private fun serverStopped(ignored: MinecraftServer) {
logger.info("Shutting down TPA++")
AsyncCommandHelper.stopTickLoop()
coroutineContext.cancel()
}
}
28 changes: 21 additions & 7 deletions src/main/kotlin/net/superricky/tpaplusplus/async/AsyncCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,28 @@ package net.superricky.tpaplusplus.async
import net.superricky.tpaplusplus.utility.LevelBoundVec3
import net.superricky.tpaplusplus.utility.getDimension

interface AsyncCommand {
fun checkWindupDistance(asyncCommandData: AsyncCommandData): Boolean
/**
* Abstract class for asyncCommand
*/
abstract class AsyncCommand {
protected lateinit var commandName: String

fun getCooldownTime(): Double
/**
* This function is used to check if the player's movement
* distance is within the limit when the command is executed
*/
abstract fun checkWindupDistance(asyncCommandData: AsyncCommandData): Boolean

fun getDelayTime(): Double
abstract fun getCooldownTime(): Double

fun checkWindupDistance(
abstract fun getDelayTime(): Double

/**
* Command names cannot be hot loaded
*/
fun getCommandName(): String = commandName

protected fun checkWindupDistance(
asyncCommandData: AsyncCommandData,
checkFunction: Function1<AsyncCommandData, Double>,
minDistance: Double
Expand All @@ -25,14 +39,14 @@ interface AsyncCommand {
return distance <= minDistance
}

fun getSenderDistance(asyncCommandData: AsyncCommandData): Double {
protected fun getSenderDistance(asyncCommandData: AsyncCommandData): Double {
val originPos = asyncCommandData.getPos()
val sender = asyncCommandData.getRequest().sender
val nowPos = LevelBoundVec3(sender.getDimension(), sender.pos)
return originPos.distance(nowPos)
}

fun getReceiverDistance(asyncCommandData: AsyncCommandData): Double {
protected fun getReceiverDistance(asyncCommandData: AsyncCommandData): Double {
val originPos = asyncCommandData.getPos()
val receiver = asyncCommandData.getRequest().receiver
require(receiver != null) { "Receiver not found" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,32 @@ package net.superricky.tpaplusplus.async

import kotlinx.atomicfu.AtomicBoolean
import kotlinx.atomicfu.atomic
import net.minecraft.text.Text
import net.superricky.tpaplusplus.config.CommonSpec
import net.superricky.tpaplusplus.config.Config
import net.superricky.tpaplusplus.async.request.Request
import net.superricky.tpaplusplus.utility.LevelBoundVec3
import net.superricky.tpaplusplus.utility.TextColorPallet
import net.superricky.tpaplusplus.utility.sendRemainTime
import net.superricky.tpaplusplus.utility.translateSecondToTick

class AsyncCommandData(
private val request: Request,
private val pos: LevelBoundVec3,
private val callback: Function2<AsyncCommandResult, Request, Unit>
private val asyncRequest: AsyncRequest,
private var pos: LevelBoundVec3,
private val callback: Function2<AsyncCommandEvent, AsyncCommandData, Unit>
) {
private var canceled: AtomicBoolean = atomic(false)
private var timeout = Config.getConfig()[CommonSpec.tpaTimeout].toDouble().translateSecondToTick()
private var timeout = Config.getConfig()[CommonSpec.tpaTimeout].translateSecondToTick()

fun needDelay(): Boolean = request.delay != 0.0
fun needDelay(): Boolean = asyncRequest.delay != 0.0

fun getDelay(): Double = request.delay
fun getDelay(): Double = asyncRequest.delay

fun updateDelay(delay: Double) {
request.delay = delay
asyncRequest.delay = delay
}

fun updateCooldown(cooldown: Double) {
request.cooldown = cooldown
asyncRequest.cooldown = cooldown
}

fun tick(): Boolean {
Expand All @@ -35,15 +37,32 @@ class AsyncCommandData(

fun getPos(): LevelBoundVec3 = pos

fun getRequest(): Request = request
fun getRequest(): AsyncRequest = asyncRequest

fun isCanceled(): Boolean = canceled.value

fun call(commandResult: AsyncCommandResult) {
fun call(commandResult: AsyncCommandEvent) {
if (isCanceled()) {
return
}
callback.invoke(commandResult, request)
when (commandResult) {
AsyncCommandEvent.REQUEST_OUT_DISTANCE -> {
asyncRequest.sender.sendMessage(
Text.translatable(
"command.windup.error.out_distance",
asyncRequest.commandType.handler.getCommandName()
).setStyle(TextColorPallet.error)
)
}

AsyncCommandEvent.REQUEST_UPDATE_MESSAGE -> {
asyncRequest.sender.sendRemainTime(asyncRequest.delay)
}

else -> {
callback.invoke(commandResult, this)
}
}
}

fun cancel() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package net.superricky.tpaplusplus.async

enum class AsyncCommandEvent {
REQUEST_AFTER_DELAY, // Command windup finish event, take effect
REQUEST_UPDATE_MESSAGE, // windup message update event, show message to player
REQUEST_CANCELED, // Teleport canceled
REQUEST_OUT_DISTANCE, // Move too much, fail to execute command
REQUEST_TIMEOUT, // Request out of time event
REQUEST_UNDER_COOLDOWN, // command under cooldown event
REQUEST_ACCEPTED, // teleport request accepted event
REQUEST_NOT_FOUND, // can not find teleport request event
TELEPORT_OUT_DISTANCE, // move too much when teleporting
TELEPORT_UPDATE_MESSAGE, // teleport message update event, show message to player
USELESS_VOID // Placeholders, useless return values
}
148 changes: 104 additions & 44 deletions src/main/kotlin/net/superricky/tpaplusplus/async/AsyncCommandHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import kotlinx.atomicfu.atomic
import kotlinx.coroutines.*
import net.minecraft.server.network.ServerPlayerEntity
import net.superricky.tpaplusplus.GlobalConst.ONE_SECOND
import net.superricky.tpaplusplus.config.CommonSpec
import net.superricky.tpaplusplus.config.Config
import java.util.*
import kotlin.coroutines.CoroutineContext

Expand Down Expand Up @@ -47,71 +49,129 @@ object AsyncCommandHelper : CoroutineScope {
playerData[type] = type.handler.getCooldownTime() * tickRate
}

fun asyncWindupCheck(
asyncCommandData: AsyncCommandData,
successCallback: Function1<AsyncCommandData, Unit>? = null,
errorCallback: Function1<AsyncCommandData, Unit>? = null,
progressCallback: Function1<AsyncCommandData, Unit>? = null,
delay: Double? = null
) {
val job = launch {
while (true) {
delay(tickDelay)
if (!asyncCommandData.getRequest().commandType.handler.checkWindupDistance(asyncCommandData)) {
errorCallback?.invoke(asyncCommandData)
return@launch
}
}
}
var delayTime = delay ?: asyncCommandData.getDelay()
asyncCommandData.updateDelay(delayTime)
launch {
progressCallback?.invoke(asyncCommandData)
while (true) {
delay(ONE_SECOND)
if (asyncCommandData.isCanceled()) {
return@launch
}
delayTime -= 1
asyncCommandData.updateDelay(delayTime)
progressCallback?.invoke(asyncCommandData)
if (delayTime < 1.0) {
break
}
}
delay((delayTime * ONE_SECOND).toLong())
if (asyncCommandData.isCanceled()) {
return@launch
}
job.cancel()
successCallback?.invoke(asyncCommandData)
}
}

private fun teleportPlayer(from: ServerPlayerEntity, to: ServerPlayerEntity) =
from.teleport(to.serverWorld, to.x, to.y, to.z, to.yaw, to.pitch)

fun teleport(asyncCommandData: AsyncCommandData) {
launch {
val asyncRequest = asyncCommandData.getRequest()
if (Config.getConfig()[CommonSpec.waitTimeBeforeTp] == 0.0) {
teleportPlayer(asyncRequest.from!!, asyncRequest.to!!)
return@launch
}
asyncWindupCheck(
asyncCommandData,
successCallback = {
teleportPlayer(asyncRequest.from!!, asyncRequest.to!!)
it.cancel()
},
errorCallback = {
it.call(AsyncCommandEvent.TELEPORT_OUT_DISTANCE)
it.cancel()
},
progressCallback = {
it.call(AsyncCommandEvent.TELEPORT_UPDATE_MESSAGE)
},
Config.getConfig()[CommonSpec.waitTimeBeforeTp]
)
}
}

fun schedule(request: AsyncCommandData) {
val uuid = request.getRequest().sender.uuid
val playerData = underCooldown[uuid]
if (playerData == null) {
underCooldown[uuid] = mutableMapOf()
} else if (playerData[request.getRequest().commandType] != null) {
request.updateCooldown(playerData[request.getRequest().commandType]!!)
request.call(AsyncCommandResult.UNDER_COOLDOWN)
request.call(AsyncCommandEvent.REQUEST_UNDER_COOLDOWN)
return
}
if (request.needDelay()) {
var delayTime = request.getDelay()
val job = launch {
while (true) {
delay(tickDelay)
if (!request.getRequest().commandType.handler.checkWindupDistance(request)) {
request.call(AsyncCommandResult.OUT_OF_DISTANCE)
request.cancel()
return@launch
}
}
}
launch {
request.call(AsyncCommandResult.UPDATE_DELAY_MESSAGE)
while (true) {
delay(ONE_SECOND)
if (request.isCanceled()) {
return@launch
}
delayTime -= 1
request.updateDelay(delayTime)
request.call(AsyncCommandResult.UPDATE_DELAY_MESSAGE)
if (delayTime < 1.0) {
break
}
}
delay((delayTime * ONE_SECOND).toLong())
request.call(AsyncCommandResult.AFTER_DELAY)
job.cancel()
if (!request.needDelay()) {
request.call(AsyncCommandEvent.REQUEST_AFTER_DELAY)
// If request not a teleport request, then there are no necessary to consider timeout
if (request.getRequest().isTeleportRequest()) {
requests.add(request)
}
} else {
request.call(AsyncCommandResult.AFTER_DELAY)
requests.add(request)
return
}
asyncWindupCheck(
request,
successCallback = {
it.call(AsyncCommandEvent.REQUEST_AFTER_DELAY)
if (it.getRequest().isTeleportRequest()) {
requests.add(it)
}
},
errorCallback = {
it.call(AsyncCommandEvent.REQUEST_OUT_DISTANCE)
it.cancel()
},
progressCallback = {
it.call(AsyncCommandEvent.REQUEST_UPDATE_MESSAGE)
}
)
}

fun acceptRequest(receiver: ServerPlayerEntity): AsyncCommandResult {
fun acceptRequest(receiver: ServerPlayerEntity): AsyncCommandEvent {
val request = requests.find { it.getRequest().receiver == receiver }
if (request == null) {
return AsyncCommandResult.REQUEST_NOT_FOUND
return AsyncCommandEvent.REQUEST_NOT_FOUND
}
request.cancel()
request.call(AsyncCommandResult.REQUEST_ACCEPTED)
return AsyncCommandResult.ACCEPT_SUCCESS
requests.remove(request)
request.call(AsyncCommandEvent.REQUEST_ACCEPTED)
return AsyncCommandEvent.USELESS_VOID
}

fun acceptRequest(receiver: ServerPlayerEntity, sender: ServerPlayerEntity): AsyncCommandResult {
fun acceptRequest(receiver: ServerPlayerEntity, sender: ServerPlayerEntity): AsyncCommandEvent {
val request = requests.find { it.getRequest().receiver == receiver && it.getRequest().sender == sender }
if (request == null) {
return AsyncCommandResult.REQUEST_NOT_FOUND
return AsyncCommandEvent.REQUEST_NOT_FOUND
}
request.cancel()
request.call(AsyncCommandResult.REQUEST_ACCEPTED)
return AsyncCommandResult.ACCEPT_SUCCESS
requests.remove(request)
request.call(AsyncCommandEvent.REQUEST_ACCEPTED)
return AsyncCommandEvent.USELESS_VOID
}

fun runTick() {
Expand All @@ -124,7 +184,7 @@ object AsyncCommandHelper : CoroutineScope {
}
// check timeout
if (it.tick()) {
it.call(AsyncCommandResult.TIMEOUT)
it.call(AsyncCommandEvent.REQUEST_TIMEOUT)
elementRemoved.add(it)
return@forEach
}
Expand Down

This file was deleted.

Loading

0 comments on commit c14dae7

Please sign in to comment.