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
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
javaVersion=25
mcVersion=26.3
group=dev.slne.surf.api
version=3.45.0
version=3.46.0
relocationPrefix=dev.slne.surf.api.libs
snapshot=true
snapshot=false
5 changes: 3 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ kotlinxCoroutines = "1.11.0"
kotlinx-serialization = "1.11.0"

# Packet Events
packetevents = "2.13.0"
packetevents-plugin = "2.13.0"
packetevents = "2.14.0"
packetevents-plugin = "2.14.0"

# Command API
commandapi = "12.0.0"
commandapiplugin = "12.1.0"

# LuckPerms
luckperms = "5.5"
Expand Down
4 changes: 2 additions & 2 deletions surf-api-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ val generateConstants = tasks.register("generateConstants") {
inputs.property("libs.canvas.api", libs.canvas.api.get().toString())
inputs.property("libs.velocity.api", libs.velocity.api.get().toString())
inputs.property("libs.auto.service.annotations", libs.auto.service.annotations.get().toString())
inputs.property("libs.versions.commandapi", libs.versions.commandapi.get())
inputs.property("libs.versions.commandapiplugin", libs.versions.commandapiplugin.get())
inputs.property("libs.versions.placeholder.api", libs.versions.placeholder.api.get())
inputs.property("libs.versions.luckperms", libs.versions.luckperms.get())
inputs.property(
Expand Down Expand Up @@ -163,7 +163,7 @@ val generateConstants = tasks.register("generateConstants") {
| const val MINECRAFT_VERSION = "$mcVersion"
| const val SURF_API_VERSION = "+"
|
| const val COMMAND_API_VERSION = "${libs.versions.commandapi.get()}"
| const val COMMAND_API_VERSION = "${libs.versions.commandapiplugin.get()}"
| const val PLACEHOLDER_API_VERSION = "${libs.versions.placeholder.api.get()}"
| const val LUCKPERMS_VERSION = "${libs.versions.luckperms.get()}"
| const val PACKETEVENTS_VERSION = "${libs.versions.packetevents.plugin.get()}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import dev.slne.surf.api.paper.nms.bridges.packets.entity.SurfPaperNmsSpawnPacke
import dev.slne.surf.api.paper.nms.bridges.packets.player.SurfPaperNmsPlayerChatPackets
import dev.slne.surf.api.paper.nms.bridges.packets.player.SurfPaperNmsPlayerPackets
import dev.slne.surf.api.paper.nms.bridges.packets.player.SurfPaperNmsPlayerToastPackets
import dev.slne.surf.api.paper.nms.common.scoreboard.PlayerNmsScoreboard
import dev.slne.surf.api.paper.packet.listener.listener.PacketListener
import dev.slne.surf.api.paper.region.TickThreadGuard
import dev.slne.surf.api.shared.internal.nms.NmsProviderConfig
import dev.slne.surf.api.shared.internal.nms.NmsProviderMeta
import dev.slne.surf.api.shared.internal.nms.NmsVersion
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import java.io.File
import java.net.URI
Expand Down Expand Up @@ -81,6 +83,13 @@ interface NmsProvider {
*/
fun createGlowingApi(): SurfGlowingApi

// ==================== Scoreboard ==================== //

/**
* Creates a packet-based sidebar for [player]; nothing is sent before its first update.
*/
fun createPlayerScoreboard(player: Player): PlayerNmsScoreboard

// ==================== Packet Listeners ==================== //

fun createChannelInjector(): AbstractChannelInjector<*>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* This file contains code derived from FastBoard, licensed under the MIT License.
*
* Copyright (c) 2019-2026 MrMicky
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package dev.slne.surf.api.paper.nms.common.scoreboard

import dev.slne.surf.api.paper.nms.bridges.packets.PacketOperation
import dev.slne.surf.api.paper.sidebar.SidebarLine
import net.kyori.adventure.text.Component
import org.bukkit.entity.Player
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.max

/**
* Packet-based sidebar objective of a single player.
*
* Not thread-safe: the owner serializes all calls. Nothing is sent before the first [update],
* which creates and displays the objective; after [delete], further calls do nothing.
*/
abstract class PlayerNmsScoreboard(protected val player: Player) {

val id = "sb-" + NEXT_ID.getAndIncrement()

private var shown = false
private var deleted = false
private var title: Component = Component.empty()
private var lines: List<SidebarLine> = emptyList()

/**
* Shows [title] and [lines], sending only what changed since the previous call as one bundle.
*
* [lines] is retained and must neither be modified afterwards nor exceed [MAX_LINES] entries.
*/
fun update(title: Component, lines: List<SidebarLine>) {
if (deleted) return

val operation = PacketOperation.start()
if (!shown) {
shown = true
operation.add(createObjective(title))
} else if (this.title != title) {
operation.add(updateObjective(title))
}
this.title = title

val oldLines = this.lines
this.lines = lines
for (index in 0 until max(oldLines.size, lines.size)) {
val line = lines.getOrNull(index)
if (line == null) {
operation.add(resetScore(ENTRIES[index]))
} else if (line != oldLines.getOrNull(index)) {
operation.add(setScore(ENTRIES[index], MAX_LINES - index, line))
}
}

send(operation)
}

/** Removes the objective from the client if it was shown. */
fun delete() {
if (deleted) return

deleted = true
if (shown) send(removeObjective())
}

protected abstract fun createObjective(title: Component): PacketOperation
protected abstract fun updateObjective(title: Component): PacketOperation
protected abstract fun removeObjective(): PacketOperation
protected abstract fun setScore(owner: String, score: Int, line: SidebarLine): PacketOperation
protected abstract fun resetScore(owner: String): PacketOperation

private fun send(operation: PacketOperation) {
if (!operation.isEmpty() && player.isConnected) {
operation.execute(player)
}
}

companion object {
/**
* Maximum number of sidebar entries the client renders.
*/
const val MAX_LINES = 15

private val NEXT_ID = AtomicInteger()
private val ENTRIES = List(MAX_LINES) { "§" + it.toString(16) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import dev.slne.surf.api.paper.nms.bridges.packets.player.SurfPaperNmsPlayerChat
import dev.slne.surf.api.paper.nms.bridges.packets.player.SurfPaperNmsPlayerPackets
import dev.slne.surf.api.paper.nms.bridges.packets.player.SurfPaperNmsPlayerToastPackets
import dev.slne.surf.api.paper.nms.common.*
import dev.slne.surf.api.paper.nms.common.scoreboard.PlayerNmsScoreboard
import dev.slne.surf.api.paper.packet.listener.listener.PacketListener
import dev.slne.surf.api.paper.region.TickThreadGuard
import dev.slne.surf.api.paper.server.nms.v1_21_11.bridges.*
Expand All @@ -28,8 +29,10 @@ import dev.slne.surf.api.paper.server.nms.v1_21_11.packet.lore.V1_21_11PacketLor
import dev.slne.surf.api.paper.server.nms.v1_21_11.packet.lore.V1_21_11PacketLoreRegistry
import dev.slne.surf.api.paper.server.nms.v1_21_11.reflection.V1_21_11Reflection
import dev.slne.surf.api.paper.server.nms.v1_21_11.region.V1_21_11TickThreadGuard
import dev.slne.surf.api.paper.server.nms.v1_21_11.scoreboard.V1_21_11PlayerNmsScoreboardImpl
import dev.slne.surf.api.shared.internal.nms.NmsProviderMarker
import dev.slne.surf.api.shared.internal.nms.NmsVersion
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import java.util.*

Expand Down Expand Up @@ -90,6 +93,7 @@ class V1_21_11NmsProvider(override val plugin: JavaPlugin) : NmsProvider {
V1_21_11GlowingLifecycleHandler()

override fun createGlowingApi(): SurfGlowingApi = V1_21_11SurfGlowingApiImpl
override fun createPlayerScoreboard(player: Player): PlayerNmsScoreboard = V1_21_11PlayerNmsScoreboardImpl(player)
override fun createChannelInjector(): AbstractChannelInjector<*> = V1_21_11ChannelInjector
override fun createPacketListenerApi(): InternalPacketListenerApiBridge = V1_21_11PacketListenerApiImpl()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ class V1_21_11PacketOperationImpl : PacketOperation {
return V1_21_11PacketOperationImpl(operation)
}

@JvmStatic
fun multi(operation: (player: Player, packets: LinkedList<Packet<in ClientGamePacketListener>>) -> Unit): V1_21_11PacketOperationImpl {
return V1_21_11PacketOperationImpl { player, packets ->
operation(player, packets)
packets
}
}

fun simple(packetSupplier: (Player) -> Packet<in ClientGamePacketListener>): V1_21_11PacketOperationImpl {
return V1_21_11PacketOperationImpl { player, packets ->
packets.add(packetSupplier(player))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package dev.slne.surf.api.paper.server.nms.v1_21_11.scoreboard

import dev.slne.surf.api.paper.nms.bridges.packets.PacketOperation
import dev.slne.surf.api.paper.nms.common.scoreboard.PlayerNmsScoreboard
import dev.slne.surf.api.paper.server.nms.v1_21_11.bridges.packets.V1_21_11PacketOperationImpl
import dev.slne.surf.api.paper.server.nms.v1_21_11.extensions.toNms
import dev.slne.surf.api.paper.sidebar.SidebarLine
import net.kyori.adventure.text.Component
import net.minecraft.network.chat.numbers.BlankFormat
import net.minecraft.network.chat.numbers.FixedFormat
import net.minecraft.network.protocol.game.ClientboundResetScorePacket
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket
import net.minecraft.network.protocol.game.ClientboundSetScorePacket
import net.minecraft.world.scores.DisplaySlot
import net.minecraft.world.scores.Objective
import net.minecraft.world.scores.Scoreboard
import net.minecraft.world.scores.criteria.ObjectiveCriteria
import org.bukkit.entity.Player
import java.util.*

@Suppress("ClassName")
class V1_21_11PlayerNmsScoreboardImpl(player: Player) : PlayerNmsScoreboard(player) {

override fun createObjective(title: Component): PacketOperation {
val objective = objective(title)

return V1_21_11PacketOperationImpl.multi { _, packets ->
packets.add(
ClientboundSetObjectivePacket(
objective,
ClientboundSetObjectivePacket.METHOD_ADD
)
)
packets.add(ClientboundSetDisplayObjectivePacket(DisplaySlot.SIDEBAR, objective))
}
}

override fun updateObjective(title: Component): PacketOperation {
return V1_21_11PacketOperationImpl.simple {
ClientboundSetObjectivePacket(
objective(title),
ClientboundSetObjectivePacket.METHOD_CHANGE
)
}
}

override fun removeObjective(): PacketOperation = V1_21_11PacketOperationImpl.simple {
ClientboundSetObjectivePacket(
objective(Component.empty()),
ClientboundSetObjectivePacket.METHOD_REMOVE
)
}

override fun setScore(owner: String, score: Int, line: SidebarLine): PacketOperation {
return V1_21_11PacketOperationImpl.simple {
val format = line.score?.let { FixedFormat(it.toNms()) } ?: BlankFormat.INSTANCE
ClientboundSetScorePacket(
owner,
id,
score,
Optional.of(line.text.toNms()),
Optional.of(format)
)
}
}

override fun resetScore(owner: String): PacketOperation = V1_21_11PacketOperationImpl.simple {
ClientboundResetScorePacket(owner, id)
}

private fun objective(title: Component) = Objective(
scoreboard,
id,
ObjectiveCriteria.DUMMY,
title.toNms(),
ObjectiveCriteria.RenderType.INTEGER,
false,
null
)

companion object {
private val scoreboard = Scoreboard()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import dev.slne.surf.api.paper.nms.bridges.packets.player.SurfPaperNmsPlayerChat
import dev.slne.surf.api.paper.nms.bridges.packets.player.SurfPaperNmsPlayerPackets
import dev.slne.surf.api.paper.nms.bridges.packets.player.SurfPaperNmsPlayerToastPackets
import dev.slne.surf.api.paper.nms.common.*
import dev.slne.surf.api.paper.nms.common.scoreboard.PlayerNmsScoreboard
import dev.slne.surf.api.paper.packet.listener.listener.PacketListener
import dev.slne.surf.api.paper.region.TickThreadGuard
import dev.slne.surf.api.paper.server.nms.v26_1.bridges.*
Expand All @@ -28,8 +29,10 @@ import dev.slne.surf.api.paper.server.nms.v26_1.packet.lore.V26_1PacketLoreListe
import dev.slne.surf.api.paper.server.nms.v26_1.packet.lore.V26_1PacketLoreRegistry
import dev.slne.surf.api.paper.server.nms.v26_1.reflection.V26_1Reflection
import dev.slne.surf.api.paper.server.nms.v26_1.region.V26_1TickThreadGuard
import dev.slne.surf.api.paper.server.nms.v26_1.scoreboard.V26_1PlayerNmsScoreboardImpl
import dev.slne.surf.api.shared.internal.nms.NmsProviderMarker
import dev.slne.surf.api.shared.internal.nms.NmsVersion
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import java.util.*

Expand Down Expand Up @@ -68,6 +71,7 @@ class V26_1NmsProvider(override val plugin: JavaPlugin) : NmsProvider {
override fun createPacketLoreRegistry(): PacketLoreRegistry = V26_1PacketLoreRegistry()
override fun createGlowingLifecycleHandler(): GlowingLifecycleHandler = V26_1GlowingLifecycleHandler()
override fun createGlowingApi(): SurfGlowingApi = V26_1SurfGlowingApiImpl
override fun createPlayerScoreboard(player: Player): PlayerNmsScoreboard = V26_1PlayerNmsScoreboardImpl(player)
override fun createChannelInjector(): AbstractChannelInjector<*> = V26_1ChannelInjector
override fun createPacketListenerApi(): InternalPacketListenerApiBridge = V26_1PacketListenerApiImpl()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ class V26_1PacketOperationImpl : PacketOperation {
return V26_1PacketOperationImpl(operation)
}

@JvmStatic
fun multi(operation: (player: Player, packets: LinkedList<Packet<in ClientGamePacketListener>>) -> Unit): V26_1PacketOperationImpl {
return V26_1PacketOperationImpl { player, packets ->
operation(player, packets)
packets
}
}

fun simple(packetSupplier: (Player) -> Packet<in ClientGamePacketListener>): V26_1PacketOperationImpl {
return V26_1PacketOperationImpl { player, packets ->
packets.add(packetSupplier(player))
Expand Down
Loading
Loading