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

Commit 41bcd42

Browse files
committed
feat: improve error handling and refactor silent disconnect functionality
1 parent d47897e commit 41bcd42

File tree

13 files changed

+51
-303
lines changed

13 files changed

+51
-303
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class BukkitBootstrap : PluginBootstrap {
2020
)
2121
)
2222
} catch (e: Throwable) {
23-
e.handleEventuallyFatalError({ exitProcess(it.exitCode) })
23+
e.handleEventuallyFatalError { exitProcess(it.exitCode) }
2424
}
2525
}
2626

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

Lines changed: 18 additions & 270 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,20 @@ package dev.slne.surf.cloud.bukkit
33
import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin
44
import com.github.shynixn.mccoroutine.folia.globalRegionDispatcher
55
import com.github.shynixn.mccoroutine.folia.launch
6-
import com.github.shynixn.mccoroutine.folia.ticks
76
import dev.jorel.commandapi.CommandAPIBukkit
87
import dev.jorel.commandapi.kotlindsl.*
98
import dev.slne.surf.cloud.api.client.netty.packet.fireAndForget
10-
import dev.slne.surf.cloud.api.client.paper.player.toCloudOfflinePlayer
119
import dev.slne.surf.cloud.api.common.TestPacket
1210
import dev.slne.surf.cloud.api.common.player.teleport.TeleportCause
1311
import dev.slne.surf.cloud.api.common.player.teleport.fineLocation
1412
import dev.slne.surf.cloud.api.common.player.toCloudPlayer
1513
import dev.slne.surf.cloud.api.common.server.CloudServerManager
16-
import dev.slne.surf.cloud.bukkit.player.BukkitClientCloudPlayerImpl
1714
import dev.slne.surf.cloud.core.common.handleEventuallyFatalError
1815
import dev.slne.surf.surfapi.bukkit.api.event.listen
19-
import dev.slne.surf.surfapi.core.api.messages.Colors
20-
import dev.slne.surf.surfapi.core.api.messages.CommonComponents
21-
import dev.slne.surf.surfapi.core.api.messages.adventure.buildText
22-
import dev.slne.surf.surfapi.core.api.messages.adventure.sendText
23-
import kotlinx.coroutines.async
24-
import kotlinx.coroutines.delay
2516
import net.kyori.adventure.text.Component
2617
import net.kyori.adventure.text.format.NamedTextColor
2718
import org.bukkit.Bukkit
2819
import org.bukkit.Location
29-
import org.bukkit.NamespacedKey
30-
import org.bukkit.OfflinePlayer
3120
import org.bukkit.entity.Player
3221
import org.bukkit.event.server.ServerLoadEvent
3322
import kotlin.contracts.ExperimentalContracts
@@ -38,138 +27,40 @@ class BukkitMain : SuspendingJavaPlugin() {
3827
try {
3928
bukkitCloudInstance.onLoad()
4029
} catch (t: Throwable) {
41-
t.handleEventuallyFatalError({ Bukkit.shutdown() })
30+
t.handleEventuallyFatalError { Bukkit.shutdown() }
4231
}
4332
}
4433

4534
override suspend fun onEnableAsync() {
4635
try {
4736
bukkitCloudInstance.onEnable()
4837
} catch (t: Throwable) {
49-
t.handleEventuallyFatalError({ Bukkit.shutdown() })
38+
t.handleEventuallyFatalError { Bukkit.shutdown() }
5039
}
5140

5241
var serverLoaded = false
5342
listen<ServerLoadEvent> {
54-
if (serverLoaded) {
55-
return@listen
56-
}
57-
43+
if (serverLoaded) return@listen
5844
serverLoaded = true
59-
System.err.println("Server loaded ##############################################")
60-
}
6145

62-
// TODO: does this actually delay until the server is fully loaded?
63-
launch(globalRegionDispatcher) {
64-
delay(1.ticks)
65-
try {
66-
bukkitCloudInstance.afterStart()
67-
} catch (t: Throwable) {
68-
t.handleEventuallyFatalError({ Bukkit.shutdown() })
69-
}
70-
}
71-
72-
commandTree("getServer") {
73-
literalArgument("byId") {
74-
longArgument("id") {
75-
anyExecutor { sender, args ->
76-
val id: Long by args
77-
launch {
78-
val server = CloudServerManager.retrieveServerById(id)
79-
sender.sendMessage("Server: $server")
80-
}
81-
}
82-
}
83-
}
84-
literalArgument("byCategoryAndName") {
85-
stringArgument("category") {
86-
stringArgument("name") {
87-
anyExecutor { sender, args ->
88-
val category: String by args
89-
val name: String by args
90-
launch {
91-
val server =
92-
CloudServerManager.retrieveServerByCategoryAndName(
93-
category,
94-
name
95-
)
96-
sender.sendMessage("Server: $server")
97-
}
98-
}
99-
}
100-
}
101-
}
102-
literalArgument("byName") {
103-
stringArgument("name") {
104-
anyExecutor { sender, args ->
105-
val name: String by args
106-
launch {
107-
val server = CloudServerManager.retrieveServerByName(name)
108-
sender.sendMessage("Server: $server")
109-
}
110-
}
111-
}
112-
}
113-
literalArgument("byCategory") {
114-
stringArgument("category") {
115-
anyExecutor { sender, args ->
116-
val category: String by args
117-
launch {
118-
val servers = CloudServerManager.retrieveServersByCategory(category)
119-
sender.sendMessage("Servers: $servers")
120-
}
121-
}
122-
}
123-
}
124-
literalArgument("self") {
125-
playerExecutor { player, _ ->
126-
player.sendPlainMessage("Server: ${(player.toCloudPlayer()!! as BukkitClientCloudPlayerImpl).serverUid}")
46+
launch(globalRegionDispatcher) {
47+
try {
48+
bukkitCloudInstance.afterStart()
49+
} catch (t: Throwable) {
50+
t.handleEventuallyFatalError { Bukkit.shutdown() }
12751
}
12852
}
12953
}
13054

131-
commandAPICommand("changePlayers") {
132-
integerArgument("amount", min = 0)
133-
anyExecutor { sender, args ->
134-
val amount: Int by args
135-
Bukkit.setMaxPlayers(amount)
136-
}
137-
}
138-
139-
commandAPICommand("setPdc") {
140-
namespacedKeyArgument("key")
141-
stringArgument("value")
142-
143-
playerExecutor { player, args ->
144-
val key: NamespacedKey by args
145-
val value: String by args
146-
147-
launch {
148-
player.toCloudPlayer()!!.withPersistentData {
149-
setString(key, value)
150-
}
151-
}
152-
}
153-
}
154-
155-
commandAPICommand("disconnect") {
156-
entitySelectorArgumentOnePlayer("target")
157-
adventureChatComponentArgument("reason", optional = true)
158-
159-
playerExecutor { player, args ->
160-
val target: Player by args
161-
val reason =
162-
args.getOptionalUnchecked<Component>("reason").orElse(Component.empty())
163-
164-
target.toCloudPlayer()?.disconnect(reason)
165-
player.sendMessage(
166-
Component.text(
167-
"Disconnected player ${target.name}",
168-
NamedTextColor.GREEN
169-
)
170-
)
171-
}
172-
}
55+
// TODO: does this actually delay until the server is fully loaded?
56+
// launch(globalRegionDispatcher) {
57+
// delay(1.ticks)
58+
// try {
59+
// bukkitCloudInstance.afterStart()
60+
// } catch (t: Throwable) {
61+
// t.handleEventuallyFatalError({ Bukkit.shutdown() })
62+
// }
63+
// }
17364

17465
commandAPICommand("deport") {
17566
entitySelectorArgumentOnePlayer("target")
@@ -214,155 +105,12 @@ class BukkitMain : SuspendingJavaPlugin() {
214105
}
215106
}
216107

217-
commandAPICommand("offlinePlayer") {
218-
offlinePlayerArgument("player")
219-
anyExecutor { sender, args ->
220-
val player: OfflinePlayer by args
221-
val offlinePlayer = player.toCloudOfflinePlayer()
222-
223-
launch {
224-
val displayName = async { offlinePlayer.displayName() }
225-
val lastServer = async { offlinePlayer.lastServer() }
226-
val lastSeen = async { offlinePlayer.lastSeen() }
227-
val lastIpAddress = async { offlinePlayer.latestIpAddress() }
228-
val playedBefore = async { offlinePlayer.playedBefore() }
229-
val nameHistory = async { offlinePlayer.nameHistory() }
230-
231-
sender.sendText {
232-
appendPrefix()
233-
appendNewPrefixedLine {
234-
variableKey("UUID")
235-
spacer(": ")
236-
variableValue(offlinePlayer.uuid.toString())
237-
}
238-
appendNewPrefixedLineAsync {
239-
variableKey("Display Name")
240-
spacer(": ")
241-
append(displayName.await() ?: Component.text("#Unknown"))
242-
}
243-
appendNewPrefixedLineAsync {
244-
variableKey("Last Server")
245-
spacer(": ")
246-
variableValue(lastServer.await()?.name ?: "#Unknown")
247-
}
248-
appendNewPrefixedLineAsync {
249-
variableKey("Last Seen")
250-
spacer(": ")
251-
variableValue(lastSeen.await()?.toString() ?: "#Unknown")
252-
}
253-
appendNewPrefixedLineAsync {
254-
variableKey("Last IP Address")
255-
spacer(": ")
256-
variableValue(lastIpAddress.await()?.hostAddress ?: "#Unknown")
257-
}
258-
appendNewPrefixedLineAsync {
259-
variableKey("Played Before")
260-
spacer(": ")
261-
variableValue(playedBefore.await().toString())
262-
}
263-
appendAsync {
264-
appendCollectionNewLine(nameHistory.await().names()) {(timestamp, name) ->
265-
buildText {
266-
variableKey(timestamp.toString())
267-
append(CommonComponents.MAP_SEPERATOR)
268-
variableValue(name)
269-
}
270-
}
271-
}
272-
}
273-
}
274-
}
275-
}
276-
277108
commandAPICommand("send-test-packet") {
278109
anyExecutor { sender, args ->
279110
TestPacket.random().fireAndForget()
280111
sender.sendPlainMessage("Test packet sent")
281112
}
282113
}
283-
284-
commandAPICommand("playtime") {
285-
offlinePlayerArgument("player")
286-
anyExecutor { sender, args ->
287-
val player: OfflinePlayer by args
288-
val cloudPlayer = player.toCloudOfflinePlayer()
289-
launch {
290-
val playtime = cloudPlayer.playtime()
291-
val complete = playtime.sumPlaytimes()
292-
val playtimeMap = playtime.playtimePerCategoryPerServer()
293-
294-
sender.sendText {
295-
appendPrefix()
296-
info("Playtime for player ${player.name} (${player.uniqueId})")
297-
appendNewPrefixedLine()
298-
appendNewPrefixedLine {
299-
variableKey("Total")
300-
spacer(": ")
301-
variableValue(complete.toString())
302-
}
303-
appendNewPrefixedLine()
304-
for ((group, groupServer) in playtimeMap) {
305-
appendNewPrefixedLine {
306-
spacer("- ")
307-
variableKey(group)
308-
spacer(": ")
309-
variableValue(playtime.sumByCategory(group).toString())
310-
311-
for ((serverName, playtime) in groupServer) {
312-
appendNewPrefixedLine {
313-
text(" ")
314-
variableKey(serverName)
315-
spacer(": ")
316-
variableValue(playtime.toString())
317-
}
318-
}
319-
appendNewPrefixedLine()
320-
}
321-
}
322-
}
323-
}
324-
}
325-
}
326-
327-
commandAPICommand("lastSeen") {
328-
offlinePlayerArgument("player")
329-
anyExecutor { sender, args ->
330-
val player: OfflinePlayer by args
331-
val cloudPlayer = player.toCloudOfflinePlayer()
332-
launch {
333-
val lastSeen = cloudPlayer.lastSeen()
334-
sender.sendText {
335-
appendPrefix()
336-
info("Last seen for player ${player.name} (${player.uniqueId})")
337-
appendNewPrefixedLine {
338-
variableKey("Last Seen")
339-
spacer(": ")
340-
variableValue(lastSeen?.toString() ?: "#Unknown")
341-
}
342-
}
343-
}
344-
}
345-
}
346-
347-
commandAPICommand("currentSessionDuration") {
348-
entitySelectorArgumentOnePlayer("player")
349-
anyExecutor { sender, args ->
350-
val player: Player by args
351-
val cloudPlayer = player.toCloudPlayer()
352-
launch {
353-
val currentSessionDuration = cloudPlayer?.currentSessionDuration()
354-
sender.sendText {
355-
appendPrefix()
356-
info("Current session duration for player ${player.name} (${player.uniqueId})")
357-
appendNewPrefixedLine {
358-
variableKey("Current Session Duration")
359-
spacer(": ")
360-
variableValue(currentSessionDuration?.toString() ?: "#Unknown")
361-
}
362-
}
363-
}
364-
}
365-
}
366114
}
367115

368116
@OptIn(ExperimentalContracts::class)
@@ -387,7 +135,7 @@ class BukkitMain : SuspendingJavaPlugin() {
387135
try {
388136
bukkitCloudInstance.onDisable()
389137
} catch (t: Throwable) {
390-
t.handleEventuallyFatalError({ })
138+
t.handleEventuallyFatalError {}
391139
}
392140
}
393141

surf-cloud-bukkit/src/main/kotlin/dev/slne/surf/cloud/bukkit/command/broadcast/BroadcastCommand.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import dev.slne.surf.surfapi.core.api.messages.Colors
1313
import dev.slne.surf.surfapi.core.api.messages.adventure.sendText
1414
import kotlinx.coroutines.launch
1515
import net.kyori.adventure.text.Component
16+
import net.kyori.adventure.text.TextComponent
1617
import org.bukkit.command.CommandSender
1718
import java.util.function.BiFunction
1819

@@ -136,10 +137,14 @@ private fun executeBroadcast(
136137
group: String? = null
137138
) = plugin.launch{
138139
val prefix = prefix ?: Colors.PREFIX
139-
val message = message.replaceText {
140-
it.match("(?m)^|\\A")
141-
.replacement(prefix)
142-
.replaceInsideHoverEvents(false)
140+
val message = if (message is TextComponent && !message.content().contains("\n")) {
141+
prefix.append(message)
142+
} else {
143+
message.replaceText {
144+
it.match("(?m)^|\\A")
145+
.replacement(prefix)
146+
.replaceInsideHoverEvents(false)
147+
}
143148
}
144149

145150
launch {

0 commit comments

Comments
 (0)