22
33package dev.slne.surf.surfapi.bukkit.api.util
44
5+ import com.github.shynixn.mccoroutine.folia.SuspendingPlugin
6+ import com.github.shynixn.mccoroutine.folia.entityDispatcher
7+ import com.github.shynixn.mccoroutine.folia.regionDispatcher
58import dev.slne.surf.surfapi.core.api.util.getCallerClass
9+ import kotlinx.coroutines.async
10+ import kotlinx.coroutines.awaitAll
11+ import kotlinx.coroutines.coroutineScope
12+ import kotlinx.coroutines.withContext
613import org.bukkit.Bukkit
714import org.bukkit.Chunk
815import org.bukkit.Location
916import org.bukkit.NamespacedKey
17+ import org.bukkit.entity.Entity
1018import org.bukkit.entity.Player
1119import org.bukkit.plugin.java.JavaPlugin
1220import java.util.*
1321
22+ /* *
23+ * Creates a [NamespacedKey] using the calling plugin and the given name.
24+ *
25+ * @param name The key name.
26+ * @return The created [NamespacedKey].
27+ * @throws IllegalStateException If the calling plugin cannot be determined.
28+ */
1429fun key (name : String ): NamespacedKey { // TODO: Verify if this works
1530 return NamespacedKey (getCallingPlugin(), name)
1631}
1732
33+ /* *
34+ * Retrieves the [JavaPlugin] that called this method.
35+ *
36+ * @param depth The stack trace depth to determine the caller class. Default is 1.
37+ * @return The calling [JavaPlugin].
38+ * @throws IllegalStateException If the caller class cannot be determined.
39+ */
1840fun getCallingPlugin (depth : Int = 1): JavaPlugin {
1941 val caller = getCallerClass(depth) ? : error(" Cannot determine caller class" )
2042 return JavaPlugin .getProvidingPlugin(caller)
2143}
2244
45+ /* *
46+ * Iterates over all online players and performs the given action.
47+ *
48+ * @param action The action to perform on each player.
49+ */
2350fun forEachPlayer (action : (player: Player ) -> Unit ) {
2451 Bukkit .getOnlinePlayers().forEach(action)
2552}
2653
54+ /* *
55+ * Executes a suspendable action on each online player, optionally concurrently.
56+ *
57+ * @param action The suspendable action to perform on each player.
58+ * @param concurrent If `true`, actions will run concurrently; otherwise, sequentially. Default is `false`.
59+ */
60+ suspend fun forEachPlayerInRegion (
61+ action : suspend (player: Player ) -> Unit ,
62+ concurrent : Boolean = false,
63+ ) {
64+ if (concurrent) {
65+ coroutineScope {
66+ Bukkit .getOnlinePlayers()
67+ .map {
68+ async {
69+ withContext(it.dispatcher(getCallingSuspendingPlugin())) {
70+ action(it)
71+ }
72+ }
73+ }.awaitAll()
74+ }
75+ } else {
76+ for (player in Bukkit .getOnlinePlayers()) {
77+ withContext(player.dispatcher(getCallingSuspendingPlugin())) {
78+ action(player)
79+ }
80+ }
81+ }
82+ }
83+
84+ /* *
85+ * Computes the squared distance between this location and another.
86+ *
87+ * @receiver The starting location.
88+ * @param other The target location.
89+ * @return The squared distance between the two locations.
90+ */
2791infix fun Location.distanceSqt (other : Location ): Double = distanceSquared(other)
2892
93+ /* *
94+ * Gets the chunk X coordinate of this location.
95+ */
96+ val Location .chunkX get() = blockX shr 4
97+
98+ /* *
99+ * Gets the chunk Z coordinate of this location.
100+ */
101+ val Location .chunkZ get() = blockZ shr 4
102+
103+ /* *
104+ * Gets the chunk key of this location.
105+ */
106+ val Location .chunkKey get() = Chunk .getChunkKey(this )
107+
108+ /* *
109+ * Converts an iterable of UUIDs to a list of online [Player] instances.
110+ *
111+ * @receiver The collection of UUIDs.
112+ * @return A list of [Player] instances corresponding to the UUIDs, excluding offline players.
113+ */
29114fun Iterable<UUID>.toPlayers () = mapNotNull { Bukkit .getPlayer(it) }
115+
116+ /* *
117+ * Converts a sequence of UUIDs to a sequence of online [Player] instances.
118+ *
119+ * @receiver The sequence of UUIDs.
120+ * @return A list of [Player] instances corresponding to the UUIDs, excluding offline players.
121+ */
122+ fun Sequence<UUID>.toPlayers () = mapNotNull { Bukkit .getPlayer(it) }
123+
124+ /* *
125+ * Checks if the player can see the specified location.
126+ *
127+ * @receiver The player.
128+ * @param location The location to check.
129+ * @return `true` if the player can see the location, `false` otherwise.
130+ */
30131fun Player.seesLocation (location : Location ): Boolean {
31132 val sameWorld = world == location.world
32133 val chunkSent = isChunkSent(Chunk .getChunkKey(location))
@@ -35,3 +136,47 @@ fun Player.seesLocation(location: Location): Boolean {
35136
36137 return this .world == location.world && this .isChunkSent(Chunk .getChunkKey(location))
37138}
139+
140+ /* *
141+ * Checks if the player can see the specified location based on chunk visibility.
142+ *
143+ * @receiver The player.
144+ * @param location The location to check.
145+ * @return `true` if the player can see the chunk containing the location, `false` otherwise.
146+ */
147+ fun Player.seesLocation2 (location : Location ): Boolean =
148+ this .world == location.world && location.world.getPlayersSeeingChunk(
149+ location.chunkX,
150+ location.chunkZ
151+ ).contains(this )
152+
153+ /* *
154+ * Retrieves the coroutine dispatcher for this entity.
155+ *
156+ * @receiver The entity.
157+ * @param plugin The suspending plugin instance. Defaults to the calling suspending plugin.
158+ * @return The entity's coroutine dispatcher.
159+ */
160+ fun Entity.dispatcher (
161+ plugin : SuspendingPlugin = getCallingSuspendingPlugin(),
162+ ) = plugin.entityDispatcher(this )
163+
164+ /* *
165+ * Retrieves the coroutine dispatcher for this location.
166+ *
167+ * @receiver The location.
168+ * @param plugin The suspending plugin instance. Defaults to the calling suspending plugin.
169+ * @return The region's coroutine dispatcher.
170+ */
171+ fun Location.dispatcher (
172+ plugin : SuspendingPlugin = getCallingSuspendingPlugin(),
173+ ) = plugin.regionDispatcher(this )
174+
175+ /* *
176+ * Retrieves the calling suspending plugin.
177+ *
178+ * @return The calling [SuspendingPlugin].
179+ * @throws IllegalStateException If the calling plugin cannot be determined.
180+ */
181+ private fun getCallingSuspendingPlugin () = getCallingPlugin(2 ) as ? SuspendingPlugin
182+ ? : error(" Cannot determine plugin" )
0 commit comments