Skip to content
This repository was archived by the owner on Dec 10, 2025. It is now read-only.

Commit 93ddb8b

Browse files
committed
feat: introduce toast notification support and clean up unused punishment handling logic
- Added toast notification framework via `NetworkToast` and `SendToastPacket`, with platform-specific implementations. - Removed unused `IpAddressPunishmentHandler` and redundant logic in punishment-related routes. - Updated UUID handling to use `SerializableStringUUID` for `Route` classes. - Refined logging and streamlined `TestStandalonePlugin` functionality.
1 parent c266283 commit 93ddb8b

File tree

30 files changed

+346
-123
lines changed

30 files changed

+346
-123
lines changed

surf-cloud-api/surf-cloud-api-common/src/main/kotlin/dev/slne/surf/cloud/api/common/player/CloudPlayer.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import dev.slne.surf.cloud.api.common.player.ppdc.PersistentPlayerDataContainer
77
import dev.slne.surf.cloud.api.common.player.teleport.TeleportCause
88
import dev.slne.surf.cloud.api.common.player.teleport.TeleportFlag
99
import dev.slne.surf.cloud.api.common.player.teleport.WorldLocation
10+
import dev.slne.surf.cloud.api.common.player.toast.NetworkToast
1011
import dev.slne.surf.cloud.api.common.server.CloudServer
1112
import dev.slne.surf.surfapi.core.api.messages.adventure.buildText
1213
import io.netty.buffer.ByteBuf
@@ -190,6 +191,10 @@ interface CloudPlayer : Audience, OfflineCloudPlayer { // TODO: conversation but
190191

191192
suspend fun hasPermission(permission: String): Boolean
192193

194+
fun sendToast(toast: NetworkToast)
195+
fun sendToast(builder: NetworkToast.Builder.() -> Unit) =
196+
sendToast(NetworkToast.create(builder))
197+
193198
companion object {
194199
operator fun get(uuid: UUID) = CloudPlayerManager.getPlayer(uuid)
195200
operator fun get(name: String) = CloudPlayerManager.getPlayer(name)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package dev.slne.surf.cloud.api.common.player.toast
2+
3+
import dev.slne.surf.cloud.api.common.netty.network.codec.ByteBufCodecs
4+
import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec
5+
import dev.slne.surf.cloud.api.common.netty.network.codec.composite
6+
import dev.slne.surf.cloud.api.common.util.ByIdMap
7+
import dev.slne.surf.cloud.api.common.util.IdRepresentable
8+
import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder
9+
import net.kyori.adventure.key.Key
10+
import net.kyori.adventure.text.Component
11+
12+
data class NetworkToast(
13+
val icon: Key,
14+
val title: Component,
15+
val frame: Frame = Frame.TASK,
16+
) {
17+
companion object {
18+
val STREAM_CODEC = StreamCodec.composite(
19+
ByteBufCodecs.KEY_CODEC,
20+
NetworkToast::icon,
21+
ByteBufCodecs.COMPONENT_CODEC,
22+
NetworkToast::title,
23+
Frame.STREAM_CODEC,
24+
NetworkToast::frame,
25+
::NetworkToast,
26+
)
27+
28+
inline fun create(block: Builder.() -> Unit) = Builder().apply(block).build()
29+
inline operator fun invoke(block: Builder.() -> Unit) = Builder().apply(block).build()
30+
}
31+
32+
class Builder @PublishedApi internal constructor() {
33+
private var icon: Key? = null
34+
private var title: Component? = null
35+
private var frame: Frame = Frame.TASK
36+
37+
fun icon(icon: Key) {
38+
this.icon = icon
39+
}
40+
41+
fun title(title: Component) {
42+
this.title = title
43+
}
44+
45+
fun title(title: SurfComponentBuilder.() -> Unit) = title(SurfComponentBuilder(title))
46+
47+
fun frame(frame: Frame) {
48+
this.frame = frame
49+
}
50+
51+
fun build() = NetworkToast(
52+
icon = icon ?: error("Icon is required"),
53+
title = title ?: error("Title is required"),
54+
frame = frame,
55+
)
56+
}
57+
58+
enum class Frame(override val id: Int) : IdRepresentable {
59+
TASK(0),
60+
CHALLENGE(1),
61+
GOAL(2);
62+
63+
companion object {
64+
val BY_ID = IdRepresentable.enumIdMap<Frame>(ByIdMap.OutOfBoundsStrategy.ZERO)
65+
val STREAM_CODEC = IdRepresentable.codec(BY_ID)
66+
}
67+
}
68+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dev.slne.surf.cloud.api.common.util
2+
3+
import dev.slne.surf.cloud.api.common.netty.network.codec.StreamCodec
4+
import dev.slne.surf.cloud.api.common.netty.protocol.buffer.readVarInt
5+
import dev.slne.surf.cloud.api.common.netty.protocol.buffer.writeVarInt
6+
import dev.slne.surf.cloud.api.common.util.ByIdMap.OutOfBoundsStrategy
7+
import io.netty.buffer.ByteBuf
8+
9+
interface IdRepresentable {
10+
val id: Int
11+
12+
companion object {
13+
14+
fun <E> codec(itemLookup: (Int) -> E): IdRepresentableStreamCodec<E> where E : IdRepresentable {
15+
return IdRepresentableStreamCodec(itemLookup)
16+
}
17+
18+
fun <E : IdRepresentable> continuousIdMap(
19+
values: Array<E>,
20+
outOfBoundsStrategy: OutOfBoundsStrategy
21+
) = ByIdMap.continuous(IdRepresentable::id, values, outOfBoundsStrategy)
22+
23+
inline fun <reified E> enumIdMap(
24+
outOfBoundsStrategy: OutOfBoundsStrategy
25+
) where E : IdRepresentable, E : Enum<E> =
26+
ByIdMap.continuous(IdRepresentable::id, enumValues<E>(), outOfBoundsStrategy)
27+
28+
29+
class IdRepresentableStreamCodec<S : IdRepresentable>(
30+
private val itemLookup: (Int) -> S,
31+
) : StreamCodec<ByteBuf, S> {
32+
33+
override fun decode(buf: ByteBuf): S {
34+
return itemLookup(buf.readVarInt())
35+
}
36+
37+
override fun encode(buf: ByteBuf, value: S) {
38+
buf.writeVarInt(value.id)
39+
}
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package dev.slne.surf.cloud.api.server.command.argument
2+
3+
import com.mojang.brigadier.LiteralMessage
4+
import com.mojang.brigadier.StringReader
5+
import com.mojang.brigadier.arguments.ArgumentType
6+
import com.mojang.brigadier.context.CommandContext
7+
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType
8+
import com.mojang.brigadier.suggestion.Suggestions
9+
import com.mojang.brigadier.suggestion.SuggestionsBuilder
10+
import dev.slne.surf.cloud.api.common.player.CloudPlayer
11+
import dev.slne.surf.cloud.api.server.command.ArgumentSuggestion
12+
import java.util.concurrent.CompletableFuture
13+
14+
class CloudPlayerArgumentType private constructor() : ArgumentType<CloudPlayer> {
15+
16+
companion object {
17+
val NO_PLAYER_FOUND = SimpleCommandExceptionType(LiteralMessage("No player was found"))
18+
19+
fun player(): CloudPlayerArgumentType {
20+
return CloudPlayerArgumentType()
21+
}
22+
23+
fun getPlayer(context: CommandContext<*>, name: String): CloudPlayer {
24+
return context.getArgument(name, CloudPlayer::class.java)
25+
}
26+
}
27+
28+
override fun parse(reader: StringReader): CloudPlayer {
29+
val playerName = reader.readUnquotedString()
30+
val player = CloudPlayer[playerName]
31+
32+
if (player == null) {
33+
throw NO_PLAYER_FOUND.create()
34+
}
35+
36+
return player
37+
}
38+
39+
override fun <S : Any> listSuggestions(
40+
context: CommandContext<S>,
41+
builder: SuggestionsBuilder
42+
): CompletableFuture<Suggestions> {
43+
val playerNames = CloudPlayer.all().map(CloudPlayer::name)
44+
return ArgumentSuggestion.suggestStrings(playerNames, builder)
45+
}
46+
}

surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/netty/network/BukkitSpecificPacketListenerExtension.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import dev.slne.surf.cloud.api.common.player.teleport.TeleportCause
77
import dev.slne.surf.cloud.api.common.player.teleport.TeleportFlag
88
import dev.slne.surf.cloud.api.common.player.teleport.WorldLocation
99
import dev.slne.surf.cloud.api.common.player.toCloudPlayer
10+
import dev.slne.surf.cloud.api.common.player.toast.NetworkToast
1011
import dev.slne.surf.cloud.api.common.util.observer.observingFlow
1112
import dev.slne.surf.cloud.bukkit.listener.player.SilentDisconnectListener
13+
import dev.slne.surf.cloud.bukkit.player.BukkitClientCloudPlayerImpl
1214
import dev.slne.surf.cloud.bukkit.plugin
1315
import dev.slne.surf.cloud.core.client.netty.network.PlatformSpecificPacketListenerExtension
1416
import dev.slne.surf.cloud.core.client.server.ClientCloudServerImpl
@@ -85,14 +87,20 @@ class BukkitSpecificPacketListenerExtension : PlatformSpecificPacketListenerExte
8587
return player.teleportAsync(targetPlayer.location).await()
8688
}
8789

90+
override fun sendToast(uuid: UUID, toast: NetworkToast) {
91+
val player = uuid.toCloudPlayer() as? BukkitClientCloudPlayerImpl ?: return
92+
player.sendToast0(toast)
93+
}
94+
8895
override fun triggerShutdown() {
8996
plugin.launch(plugin.globalRegionDispatcher) {
9097
Bukkit.shutdown()
9198
}
9299
}
93100

94101
override fun restart() {
95-
server.restart()
102+
// server.restart() TODO: figure out a way to restart a server without relying on Pterodactyl
103+
shutdown()
96104
}
97105

98106
override fun shutdown() {

surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/player/BukkitClientCloudPlayerImpl.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@ import dev.slne.surf.cloud.api.client.paper.toLocation
77
import dev.slne.surf.cloud.api.common.player.teleport.TeleportCause
88
import dev.slne.surf.cloud.api.common.player.teleport.TeleportFlag
99
import dev.slne.surf.cloud.api.common.player.teleport.WorldLocation
10+
import dev.slne.surf.cloud.api.common.player.toast.NetworkToast
1011
import dev.slne.surf.cloud.bukkit.listener.player.SilentDisconnectListener
1112
import dev.slne.surf.cloud.core.client.player.ClientCloudPlayerImpl
1213
import dev.slne.surf.cloud.core.common.netty.network.protocol.running.DisconnectPlayerPacket
1314
import dev.slne.surf.cloud.core.common.netty.network.protocol.running.SilentDisconnectPlayerPacket
15+
import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution
16+
import dev.slne.surf.surfapi.bukkit.api.nms.bridges.packets.player.toast.Toast
17+
import dev.slne.surf.surfapi.core.api.util.logger
18+
import io.papermc.paper.advancement.AdvancementDisplay
19+
import io.papermc.paper.registry.RegistryAccess
20+
import io.papermc.paper.registry.RegistryKey
1421
import kotlinx.coroutines.future.await
1522
import net.kyori.adventure.text.Component
1623
import org.bukkit.Bukkit
@@ -48,4 +55,41 @@ class BukkitClientCloudPlayerImpl(uuid: UUID, name: String) : ClientCloudPlayerI
4855
*bukkitTeleportFlags
4956
).await()
5057
}
58+
59+
override fun sendToast(toast: NetworkToast) {
60+
if (!sendToast0(toast)) {
61+
super.sendToast(toast)
62+
}
63+
}
64+
65+
@OptIn(NmsUseWithCaution::class)
66+
fun sendToast0(networkToast: NetworkToast): Boolean {
67+
val player = audience ?: return false
68+
69+
val toastIcon = RegistryAccess.registryAccess()
70+
.getRegistry(RegistryKey.ITEM)
71+
.get(networkToast.icon)
72+
73+
if (toastIcon == null) {
74+
log.atWarning()
75+
.log("No item found for icon ${networkToast.icon}")
76+
77+
return false
78+
}
79+
80+
val frame = when (networkToast.frame) {
81+
NetworkToast.Frame.TASK -> AdvancementDisplay.Frame.TASK
82+
NetworkToast.Frame.CHALLENGE -> AdvancementDisplay.Frame.CHALLENGE
83+
NetworkToast.Frame.GOAL -> AdvancementDisplay.Frame.GOAL
84+
}
85+
86+
val toast = Toast(toastIcon.createItemStack(), networkToast.title, frame)
87+
toast.createOperation().execute(player)
88+
89+
return true
90+
}
91+
92+
companion object {
93+
private val log = logger()
94+
}
5195
}

surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/ClientRunningPacketListenerImpl.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ class ClientRunningPacketListenerImpl(
367367
TODO("Not yet implemented")
368368
}
369369

370+
override fun handleSendToast(packet: SendToastPacket) {
371+
platformExtension.sendToast(packet.uuid, packet.toast)
372+
}
373+
370374
override fun handlePacket(packet: NettyPacket) {
371375
val listeners = NettyListenerRegistry.getListeners(packet.javaClass) ?: return
372376
if (listeners.isEmpty()) return

surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/netty/network/PlatformSpecificPacketListenerExtension.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dev.slne.surf.cloud.core.client.netty.network
33
import dev.slne.surf.cloud.api.common.player.teleport.TeleportCause
44
import dev.slne.surf.cloud.api.common.player.teleport.TeleportFlag
55
import dev.slne.surf.cloud.api.common.player.teleport.WorldLocation
6+
import dev.slne.surf.cloud.api.common.player.toast.NetworkToast
67
import dev.slne.surf.cloud.core.client.server.ClientCloudServerImpl
78
import dev.slne.surf.cloud.core.common.netty.network.protocol.running.RegistrationInfo
89
import dev.slne.surf.cloud.core.common.netty.network.protocol.running.ServerboundTransferPlayerPacketResponse
@@ -38,6 +39,8 @@ interface PlatformSpecificPacketListenerExtension {
3839

3940
suspend fun teleportPlayerToPlayer(uuid: UUID, target: UUID): Boolean
4041

42+
fun sendToast(uuid: UUID, toast: NetworkToast)
43+
4144
fun setVelocitySecret(secret: ByteArray)
4245

4346
fun triggerShutdown()

surf-cloud-core/surf-cloud-core-client/src/main/kotlin/dev/slne/surf/cloud/core/client/player/ClientCloudPlayerImpl.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import dev.slne.surf.cloud.api.common.player.ppdc.PersistentPlayerDataContainer
1313
import dev.slne.surf.cloud.api.common.player.teleport.TeleportCause
1414
import dev.slne.surf.cloud.api.common.player.teleport.TeleportFlag
1515
import dev.slne.surf.cloud.api.common.player.teleport.WorldLocation
16+
import dev.slne.surf.cloud.api.common.player.toast.NetworkToast
1617
import dev.slne.surf.cloud.api.common.server.CloudServer
1718
import dev.slne.surf.cloud.api.common.util.getValue
1819
import dev.slne.surf.cloud.api.common.util.setValue
@@ -428,6 +429,10 @@ abstract class ClientCloudPlayerImpl<PlatformPlayer : Audience>(
428429
return TeleportPlayerToPlayerPacket(uuid, target.uuid).awaitOrThrow()
429430
}
430431

432+
override fun sendToast(toast: NetworkToast) {
433+
SendToastPacket(uuid, toast).fireAndForget()
434+
}
435+
431436
protected fun <R> withLuckpermsOrThrow(block: (User) -> R): R {
432437
val user = luckperms.userManager.getUser(uuid)
433438
?: error("User not found in LuckPerms! Are you sure the player is online?")

surf-cloud-core/surf-cloud-core-common/src/main/kotlin/dev/slne/surf/cloud/core/common/netty/network/ConnectionImpl.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ class ConnectionImpl(
448448
msg
449449
)
450450

451+
is SendToastPacket -> listener.handleSendToast(msg)
452+
451453
else -> listener.handlePacket(msg) // handle other packets
452454
}
453455
}
@@ -626,6 +628,7 @@ class ConnectionImpl(
626628
is ClientboundCacheRegisterAckPacket -> listener.handleCacheRegisterAck(msg)
627629
is ClientboundCacheDeltaPacket -> listener.handleCacheDelta(msg)
628630
is ClientboundCacheErrorPacket -> listener.handleCacheError(msg)
631+
is SendToastPacket -> listener.handleSendToast(msg)
629632

630633
else -> listener.handlePacket(msg)
631634
}

0 commit comments

Comments
 (0)