Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/main/kotlin/dev/slne/surf/lobby/PaperMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import dev.slne.surf.lobby.hook.hologram.SurfHologramHook
import dev.slne.surf.lobby.hook.nexo.NexoHook
import dev.slne.surf.lobby.hook.npc.SurfNpcHook
import dev.slne.surf.lobby.listener.*
import dev.slne.surf.lobby.manager.PushbackManager
import dev.slne.surf.redis.RedisApi
import dev.slne.surf.surfapi.bukkit.api.event.register
import org.bukkit.Bukkit
Expand Down Expand Up @@ -41,8 +40,6 @@ class PaperMain : SuspendingJavaPlugin() {
PlayerMoveListener.register()
EntitySpawnListener.register()

PushbackManager.startTask()

lobbyCommand()
spawnCommand()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dev.slne.surf.lobby.listener

import dev.slne.surf.lobby.lobbyConfig
import dev.slne.surf.lobby.manager.ElytraBoostManager
import dev.slne.surf.lobby.manager.PushbackManager
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerMoveEvent
Expand All @@ -23,6 +24,9 @@ object PlayerMoveListener : Listener {
return
}

PushbackManager.updateExecutorPosition(player)
PushbackManager.checkPushback(player)

@Suppress("DEPRECATION") // isOnGround is deprecated, but it works fine for our use case as it is only used to check if the player is on the ground to clear the boost, and it is not used for any critical logic.
if (player.isOnGround) {
ElytraBoostManager.clearBoost(player)
Expand Down
69 changes: 44 additions & 25 deletions src/main/kotlin/dev/slne/surf/lobby/manager/PushbackManager.kt
Original file line number Diff line number Diff line change
@@ -1,43 +1,62 @@
package dev.slne.surf.lobby.manager

import dev.slne.surf.lobby.plugin
import dev.slne.surf.lobby.utils.CircularBoundingBox
import dev.slne.surf.lobby.utils.PermissionRegistry
import dev.slne.surf.surfapi.bukkit.api.util.toPlayers
import dev.slne.surf.surfapi.core.api.util.mutableObjectSetOf
import dev.slne.surf.surfapi.core.api.util.mutableObject2ObjectMapOf
import org.bukkit.Bukkit
import org.bukkit.Effect
import org.bukkit.entity.Player
import java.util.*

object PushbackManager {
private val pushbacks = mutableObjectSetOf<UUID>()
private val boundingBoxes = mutableObject2ObjectMapOf<UUID, CircularBoundingBox>()
private val lastPushbackTimes = mutableObject2ObjectMapOf<UUID, Long>()
private const val RANGE = 3.0
private const val FORCE = -0.5
private const val Y_FORCE = 0.5

fun startTask() {
Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, {
pushbacks.toPlayers().forEach { player ->
val nearbyPlayers = player.location.getNearbyPlayers(RANGE) { other ->
other != player && !other.hasPermission(PermissionRegistry.PUSHBACK_ITEM)
}

for (nearby in nearbyPlayers) {
nearby.velocity = player.location.toVector()
.subtract(nearby.location.toVector())
.multiply(FORCE)
.setY(Y_FORCE)
}

player.world.playEffect(player.location, Effect.ENDER_SIGNAL, null)
}
}, 20, 20)
}
private const val COOLDOWN_MS = 1000L

fun add(uuid: UUID) {
pushbacks.add(uuid)
val player = Bukkit.getPlayer(uuid) ?: return
val loc = player.location
boundingBoxes[uuid] = CircularBoundingBox(loc.x, loc.z, RANGE)
}

fun remove(uuid: UUID) {
pushbacks.remove(uuid)
boundingBoxes.remove(uuid)
lastPushbackTimes.remove(uuid)
}

fun updateExecutorPosition(player: Player) {
val box = boundingBoxes[player.uniqueId] ?: return
val loc = player.location
box.updateCenter(loc.x, loc.z)
}

fun checkPushback(player: Player) {
if (player.hasPermission(PermissionRegistry.PUSHBACK_ITEM)) return

val now = System.currentTimeMillis()
val lastPushback = lastPushbackTimes[player.uniqueId]
if (lastPushback != null && now - lastPushback < COOLDOWN_MS) return

val playerLoc = player.location

for ((executorUuid, box) in boundingBoxes) {
if (executorUuid == player.uniqueId) continue
if (!box.isInside(playerLoc.x, playerLoc.z)) continue

val executor = Bukkit.getPlayer(executorUuid) ?: continue
if (executor.world != player.world) continue

player.velocity = executor.location.toVector()
.subtract(playerLoc.toVector())
.multiply(FORCE)
.setY(Y_FORCE)

executor.world.playEffect(executor.location, Effect.ENDER_SIGNAL, null)
lastPushbackTimes[player.uniqueId] = now
break
}
}
}
20 changes: 20 additions & 0 deletions src/main/kotlin/dev/slne/surf/lobby/utils/CircularBoundingBox.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.slne.surf.lobby.utils

class CircularBoundingBox(
var centerX: Double,
var centerZ: Double,
val radius: Double
) {
private val radiusSquared = radius * radius

fun isInside(x: Double, z: Double): Boolean {
val dx = x - centerX
val dz = z - centerZ
return dx * dx + dz * dz <= radiusSquared
}

fun updateCenter(x: Double, z: Double) {
centerX = x
centerZ = z
}
}