Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
28eae48
feat: add GitHub Actions workflow for publishing, enhance Redis event…
TheBjoRedCraft Dec 24, 2025
600fa08
chore: chmod
TheBjoRedCraft Dec 24, 2025
8df9116
chore: configure slneReleases repository for publishing in all subpro…
TheBjoRedCraft Dec 24, 2025
23553d8
feat: extend TabShowRedisEvent and TabHideRedisEvent from RedisEvent,…
TheBjoRedCraft Dec 24, 2025
f4ad73c
fix: use `playerCount` instead of `allPlayers.size` to determine onli…
TheBjoRedCraft Dec 26, 2025
a36b951
refactor: replace `mutableObjectSetOf` with `mutableObject2ObjectMapO…
TheBjoRedCraft Dec 28, 2025
f808c82
refactor: split `updatePlayerInTablist` into `updateOthersFor` and `u…
TheBjoRedCraft Dec 28, 2025
1a92bf8
refactor: simplify `getSeenServers` by using `toMutableObjectList`, e…
TheBjoRedCraft Dec 29, 2025
b6858f4
Revert "refactor: simplify `getSeenServers` by using `toMutableObject…
TheBjoRedCraft Dec 29, 2025
4be0ab8
Revert "refactor: split `updatePlayerInTablist` into `updateOthersFor…
TheBjoRedCraft Dec 29, 2025
22eb17a
refactor: enhance `getSeenServers` by adding base server explicitly, …
TheBjoRedCraft Dec 29, 2025
62f6d58
refactor: streamline tablist management, replace direct server refere…
TheBjoRedCraft Dec 31, 2025
cd9dd27
refactor: remove unused `TabEntryRemoveRedisEvent`, clean up tablist …
TheBjoRedCraft Dec 31, 2025
995158c
refactor: remove unused player-util functions for tab profile and gam…
TheBjoRedCraft Jan 1, 2026
7239913
refactor: migrate `TabEntryUpdateRedisEvent` to API module, improve c…
TheBjoRedCraft Jan 1, 2026
f0f585f
refactor: clean up logging in `TabRedisEventListener`, remove redunda…
TheBjoRedCraft Jan 1, 2026
250d7fa
refactor: update Gradle scripts to use `surfCoreApi` and `surfVelocit…
TheBjoRedCraft Jan 3, 2026
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
102 changes: 102 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Publish to Maven & Create GitHub Release

on:
push:
branches:
- 'version/*'
workflow_dispatch:

env:
SLNE_SNAPSHOTS_REPO_USERNAME: ${{ secrets.SLNE_SNAPSHOTS_REPO_USERNAME }}
SLNE_SNAPSHOTS_REPO_PASSWORD: ${{ secrets.SLNE_SNAPSHOTS_REPO_PASSWORD }}
SLNE_RELEASES_REPO_USERNAME: ${{ secrets.SLNE_RELEASES_REPO_USERNAME }}
SLNE_RELEASES_REPO_PASSWORD: ${{ secrets.SLNE_RELEASES_REPO_PASSWORD }}
MODULE_REGEX: "surf-tab-api.*-all\\.jar$|surf-tab-velocity.*-all\\.jar$"
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name MODULE_REGEX suggests it should be a regex pattern, but it's actually used with grep -E which expects an extended regex pattern. The current value uses shell filename patterns (ending with $) which is correct, but the name could be more descriptive such as MODULE_JAR_PATTERN to clarify its purpose.

Copilot uses AI. Check for mistakes.
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}

jobs:
build:
runs-on: ubuntu-latest
environment: production
steps:
- name: Collect Workflow Telemetry
uses: catchpoint/workflow-telemetry-action@v2

- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: gradle-${{ runner.os }}-

- name: Setup JDK
uses: actions/setup-java@v4
with:
distribution: 'graalvm'
java-version: '24'

- name: Build all modules with Gradle
run: ./gradlew build shadowJar --parallel --no-scan

- name: Publish all modules to Maven
run: ./gradlew publish --parallel --no-scan

- name: Extract Project Version and Snapshot Flag from Gradle
id: get_version
run: |
VERSION=$(./gradlew properties --no-daemon \
| grep '^version:' \
| awk '{print $2}')
SNAPSHOT_FLAG=$(./gradlew properties --no-daemon \
| grep '^snapshot:' \
| awk '{print $2}')
if [ "$SNAPSHOT_FLAG" = "true" ]; then
VERSION="${VERSION}-SNAPSHOT"
fi
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "SNAPSHOT_FLAG=$SNAPSHOT_FLAG" >> $GITHUB_ENV

- name: Determine release flags
run: |
CURRENT_BRANCH=${GITHUB_REF#refs/heads/}
# prerelease only for snapshots
if [ "${SNAPSHOT_FLAG}" = "true" ]; then
echo "PRERELEASE=true" >> $GITHUB_ENV
else
echo "PRERELEASE=false" >> $GITHUB_ENV
fi
# make_latest false for snapshots or non-default branches
if [ "${SNAPSHOT_FLAG}" = "true" ] || [ "${CURRENT_BRANCH}" != "${DEFAULT_BRANCH}" ]; then
echo "MAKE_LATEST=false" >> $GITHUB_ENV
else
echo "MAKE_LATEST=true" >> $GITHUB_ENV
fi

- name: Find and filter JAR files
id: find_jars
run: |
echo "JAR_FILES<<EOF" >> $GITHUB_ENV
find . -path "**/build/libs/*.jar" \
| grep -E "${{ env.MODULE_REGEX }}" \
>> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ env.VERSION }}
name: Release ${{ env.VERSION }}
draft: false
prerelease: ${{ env.PRERELEASE }}
make_latest: ${{ env.MAKE_LATEST }}
files: ${{ env.JAR_FILES }}
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 changes: 15 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import dev.slne.surf.surfapi.gradle.util.slneReleases

buildscript {
repositories {
gradlePluginPortal()
maven("https://repo.slne.dev/repository/maven-public/") { name = "maven-public" }
}
dependencies {
classpath("dev.slne.surf:surf-api-gradle-plugin:1.21.11-1.7.0")
classpath("dev.slne.surf:surf-api-gradle-plugin:1.21.11+")
}
}

allprojects {
group = "dev.slne.surf.tab"
version = findProperty("version") as String
}

subprojects {
afterEvaluate {
plugins.withType<PublishingPlugin> {
configure<PublishingExtension> {
repositories {
slneReleases()
}
}
}
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
kotlin.code.style=official
kotlin.stdlib.default.dependency=false
org.gradle.parallel=true
version=1.21.11-1.0.0-SNAPSHOT
version=1.21.11-1.0.2-SNAPSHOT
Empty file modified gradlew
100644 → 100755
Empty file.
7 changes: 6 additions & 1 deletion surf-tab-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
plugins {
id("dev.slne.surf.surfapi.gradle.velocity")
id("dev.slne.surf.surfapi.gradle.core")
}


surfCoreApi {
withSurfRedis()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.slne.surf.tab.api.redis

import dev.slne.surf.redis.event.RedisEvent
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import java.util.*

@Serializable
data class TabEntryUpdateRedisEvent(
val toUpdateUuid: @Contextual UUID
) : RedisEvent()
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dev.slne.surf.tab.api.redis

import dev.slne.surf.redis.event.RedisEvent
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import java.util.*

@Serializable
data class TabHideRedisEvent(
val player: @Contextual UUID,
val toHide: @Contextual UUID
) : RedisEvent()
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dev.slne.surf.tab.api.redis

import dev.slne.surf.redis.event.RedisEvent
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import java.util.*

@Serializable
data class TabShowRedisEvent(
val player: @Contextual UUID,
val toShow: @Contextual UUID
) : RedisEvent()
11 changes: 8 additions & 3 deletions surf-tab-velocity/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,27 @@ repositories {
maven("https://repo.william278.net/releases/")
}

surfVelocityApi {
withSurfRedis()
}

velocityPluginFile {
main = "dev.slne.surf.tab.velocity.VelocityMain"
authors = listOf("red")

pluginDependencies {
register("miniplaceholders")
register("luckperms")
register("surf-clan-velocity") {
optional = true
}
}
}

dependencies {
compileOnly(libs.mini.placeholders)
compileOnly(libs.mini.placeholders.kotlin)
compileOnly(libs.luckperms.api)

implementation("dev.slne:surf-redis:1.0.0-20251223.105653-21")

api(project(":surf-tab-api"))
compileOnly("dev.slne.surf.clan:surf-clan-api:1.21.11-1.3.0-SNAPSHOT")
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent
import com.velocitypowered.api.plugin.PluginContainer
import com.velocitypowered.api.plugin.annotation.DataDirectory
import com.velocitypowered.api.proxy.ProxyServer
import dev.slne.redis.RedisApi
import dev.slne.clan.api.ClanModificationListener
import dev.slne.clan.api.surfClanApi
import dev.slne.surf.redis.RedisApi
import dev.slne.surf.tab.api.redis.TabEntryUpdateRedisEvent
import dev.slne.surf.tab.velocity.command.surfTabCommand
import dev.slne.surf.tab.velocity.config.TablistConfigProvider
import dev.slne.surf.tab.velocity.hook.LuckPermsHook
Expand All @@ -33,7 +36,7 @@ class VelocityMain @Inject constructor(
@Subscribe
fun onInitialization(event: ProxyInitializeEvent) {
instance = this
redisApi = RedisApi.create(dataPath)
redisApi = RedisApi.create()

surfTabCommand()
LuckPermsHook.load()
Expand All @@ -43,6 +46,12 @@ class VelocityMain @Inject constructor(

plugin.proxy.eventManager.register(plugin, ConnectionListener())
redisApi.freezeAndConnect()

surfClanApi.addClanModificationListener(ClanModificationListener {
it.members.map { member -> member.uuid }.forEach { memberUuid ->
redisApi.publishEvent(TabEntryUpdateRedisEvent(memberUuid))
}
})
}

@Subscribe
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import dev.jorel.commandapi.kotlindsl.anyExecutor
import dev.jorel.commandapi.kotlindsl.commandTree
import dev.jorel.commandapi.kotlindsl.literalArgument
import dev.slne.surf.surfapi.core.api.messages.adventure.sendText
import dev.slne.surf.tab.velocity.plugin
import dev.slne.surf.tab.velocity.service.tablistService
import dev.slne.surf.tab.velocity.tablistConfiguration

fun surfTabCommand() = commandTree("surftab") {
Expand All @@ -15,10 +13,6 @@ fun surfTabCommand() = commandTree("surftab") {
anyExecutor { executor, _ ->
tablistConfiguration.reload()

plugin.proxy.allPlayers.forEach {
tablistService.updatePlayerInTablist(it)
}

executor.sendText {
appendPrefix()
success("Die Tablist wurde neu geladen.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,5 @@ import org.spongepowered.configurate.objectmapping.ConfigSerializable
@ConfigSerializable
data class TablistConfig(
val header: String = "<br><#6EA6D9> CASTCRAFTER <br><#6EA6D9>COMMUNITY SERVER<br><br><#59CCF2><date><gray> - <#59CCF2><time> <gray><> <#59CCF2><players_online><gray> / <#59CCF2><players_max><br><br>",
val footer: String = "<br><#59CCF2>Du befindest dich auf <#f9c353><server><br><#6EA6D9>ᴄᴀѕᴛᴄʀᴀꜰᴛᴇʀ.ᴅᴇ<br>",
val nameFormat: String = "<luckperms_prefix><player_name><luckperms_suffix>",
val groups: List<TablistGroupConfig> = listOf(
TablistGroupConfig(
"testserver",
listOf("test-server01", "cloud-test02")
)
)
val footer: String = "<br><#59CCF2>Du befindest dich auf <#f9c353><server><br><#6EA6D9>ᴄᴀѕᴛᴄʀᴀꜰᴛᴇʀ.ᴅᴇ<br>"
)
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
package dev.slne.surf.tab.velocity.hook

import dev.slne.surf.tab.api.redis.TabEntryUpdateRedisEvent
import dev.slne.surf.tab.velocity.plugin
import dev.slne.surf.tab.velocity.service.tablistService
import dev.slne.surf.tab.velocity.redisApi
import kotlinx.coroutines.future.await
import net.luckperms.api.LuckPermsProvider
import net.luckperms.api.event.node.NodeAddEvent
import net.luckperms.api.event.node.NodeRemoveEvent
import net.luckperms.api.model.user.User
import java.util.*
import kotlin.jvm.optionals.getOrNull

object LuckPermsHook {
private val luckPerms by lazy {
LuckPermsProvider.get()
}

fun getWeight(player: UUID) =
luckPerms.userManager.getUser(player)?.cachedData?.metaData?.getMetaValue("weight")
?.toInt() ?: 0
suspend fun getPrefix(player: UUID) =
getOrLoadUser(player).primaryGroup.let {
luckPerms.groupManager.getGroup(it)?.cachedData?.metaData?.prefix ?: ""
}

suspend fun getSuffix(player: UUID) =
getOrLoadUser(player).primaryGroup.let {
luckPerms.groupManager.getGroup(it)?.cachedData?.metaData?.suffix ?: ""
}

suspend fun getWeight(player: UUID) =
getOrLoadUser(player).primaryGroup.let {
luckPerms.groupManager.getGroup(it)?.weight?.orElse(0) ?: 0
}

fun load() {
luckPerms.eventBus.subscribe(plugin, NodeAddEvent::class.java) { event ->
Expand All @@ -30,10 +42,12 @@ object LuckPermsHook {
}
}

private fun updatePlayerInTablist(user: User) {
val player = plugin.proxy.getPlayer(user.uniqueId).getOrNull() ?: return
tablistService.updatePlayerInTablist(player)
suspend fun getOrLoadUser(player: UUID): User {
return luckPerms.userManager.getUser(player) ?: luckPerms.userManager.loadUser(player)
.await()
Comment on lines +46 to +47
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getOrLoadUser function can return null from getUser and fall back to loadUser, but there's no null check after the await() call. If loadUser().await() also returns null, this will cause a NullPointerException. Consider adding a null safety check or documenting that loadUser is guaranteed to return a non-null User.

Suggested change
return luckPerms.userManager.getUser(player) ?: luckPerms.userManager.loadUser(player)
.await()
val existingUser = luckPerms.userManager.getUser(player)
if (existingUser != null) {
return existingUser
}
val loadedUser = luckPerms.userManager.loadUser(player).await()
return requireNotNull(loadedUser) {
"LuckPerms returned null when loading user data for '$player'"
}

Copilot uses AI. Check for mistakes.
}


private fun updatePlayerInTablist(user: User) {
redisApi.publishEvent(TabEntryUpdateRedisEvent(user.uniqueId))
}
}
Loading
Loading