Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scoreboard Creation #79

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
83a5624
Added Static Scoreboard base
SamuSonoIo Nov 18, 2024
b093f41
Scoreboard as an object
SamuSonoIo Nov 18, 2024
21729e1
Added whitespace implementation
SamuSonoIo Nov 18, 2024
c8d6cb2
Fixed a few things, added some comments
SamuSonoIo Nov 19, 2024
62bf40c
Removed White Space thing, no need to use that
SamuSonoIo Nov 19, 2024
ee3a85a
Experimenting with abstract
SamuSonoIo Nov 19, 2024
d42cdb1
Removed Abstract class, reworked code almost from scratch
SamuSonoIo Nov 19, 2024
2516fb0
Started working on Dynamic Scoreboard
SamuSonoIo Nov 19, 2024
59c40e0
Updated both dynamic and static scoreboard
SamuSonoIo Nov 19, 2024
870dcbb
Fixed error while clearing scoreboards
SamuSonoIo Nov 19, 2024
a9eb4a1
Updated Method assignPlayer
SamuSonoIo Nov 20, 2024
6f31bb5
Completly remade Scoreboard System
SamuSonoIo Nov 20, 2024
f502191
Fixed UpdateLines, Added more features, renamed class and fixed some …
SamuSonoIo Nov 21, 2024
e259e60
(fix) Problem when removing scoreboard
SamuSonoIo Dec 9, 2024
c0df521
(fix) Fixed a few issues
SamuSonoIo Dec 9, 2024
9cf9a01
Updated README.md
SamuSonoIo Dec 10, 2024
c3f435f
Hopefully fixed sorting system
SamuSonoIo Dec 15, 2024
4fd1310
Reworked setAll.
SamuSonoIo Dec 16, 2024
cc3f8e2
Final commit
SamuSonoIo Dec 16, 2024
c594f59
Fixed conflict issues
SamuSonoIo Dec 16, 2024
f519863
Fixed Readme
SamuSonoIo Dec 16, 2024
adeffe9
Reworked setAll.
SamuSonoIo Dec 16, 2024
840ee29
Delete tatus
SamuSonoIo Dec 16, 2024
f6ba2e1
Update README.md
SamuSonoIo Dec 16, 2024
3cdba86
Renamed methods,
SamuSonoIo Dec 16, 2024
d1e2156
Fixed issues
SamuSonoIo Dec 16, 2024
c7fa154
Minimessage implementation
SamuSonoIo Dec 17, 2024
4284efa
Implemented BelowName
SamuSonoIo Dec 18, 2024
6c09589
Minor syntax change
SamuSonoIo Dec 29, 2024
29e8065
Renamed class, changed PlayerList class
SamuSonoIo Jan 5, 2025
12bb41f
Compacted everything into one class, fixed some issues.
SamuSonoIo Jan 10, 2025
a8d7cc7
Removed some methods, fixed issue with overriding prefix and suffix
SamuSonoIo Jan 10, 2025
7a5cf15
Updated ReadME
SamuSonoIo Jan 10, 2025
8b73917
Update README.md
SamuSonoIo Jan 10, 2025
ca1f6ad
Update README.md
SamuSonoIo Jan 10, 2025
0264c4c
Update README.md
SamuSonoIo Jan 11, 2025
9d0a4ef
Update README.md
SamuSonoIo Jan 13, 2025
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
168 changes: 153 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ Maven
</repository>

<dependency>
<groupId>gg.flyte</groupId>
<artifactId>twilight</artifactId>
<version>1.1.17</version>
<groupId>gg.flyte</groupId>
<artifactId>twilight</artifactId>
<version>1.1.18</version>
</dependency>
```

Expand All @@ -33,14 +33,14 @@ maven {
url "https://repo.flyte.gg/releases"
}

implementation "gg.flyte:twilight:1.1.17"
implementation "gg.flyte:twilight:1.1.18"
```

Gradle (Kotlin DSL)
```kotlin
maven("https://repo.flyte.gg/releases")

implementation("gg.flyte:twilight:1.1.17")
implementation("gg.flyte:twilight:1.1.18")
```

Certain features of Twilight require configuration, which can be done via the Twilight class. To set up a Twilight class instance, you can use the `twilight` function as shown below:
Expand Down Expand Up @@ -180,7 +180,7 @@ event<ChatClickEvent> {
if (data[0] != "openGUI") return@event
when (data[1]) {
"warps" -> GUIManager.openWarps(player)
...
...
}
}

Expand Down Expand Up @@ -264,7 +264,7 @@ repeat(5, 10, TimeUnit.SECONDS, true) {
}
```

> Is Twilight's `repeat` conflicting with Kotlin's `repeat`? As an alternative, you can use `repeatingTask`.
> Is Twilight's `repeat` conflicting with Kotlin's `repeat`? As an alternative, you can use `repeatingTask`.

You can chain tasks together using `onComplete` to nicely nest sync/async executions. Here's an example:
```kotlin
Expand All @@ -290,6 +290,144 @@ delay(20, TimeUnit.SECONDS) {
```

> Currently, onComplete is incompatible with repeating tasks.
>
# Scoreboard System

The Twilight Scoreboard system provides an easy-to-use solution for managing all the types of scoreboard you will probably ever need in your plugin.

It also provides a system to manage PlayerList (also known as TabList) and Prefix/Suffix system.
Twilight's scoreboard system uses MiniMessage to make everything easier to create without having to deal with thousands of components.
No need to serialize or deserialize anything, just use the tags you like!

### Usage Examples

**SIDEBAR CREATION**

Creating a sidebar is straightforward. You can display static content with just a few lines of code:

```kotlin
// Keep track of all scoreboards with a Map
private val scoreboards = mutableMapOf<Player, TwilightScoreboard>()

val join = event<PlayerJoinEvent> {
// Create a new instance of TwilightScoreboard when a player joins the server
val scoreboard = TwilightScoreboard(player)

scoreboard.apply {
// Set the title of the Sidebar
updateSidebarTitle("<blue><bold>TWILIGHT</bold></blue>")

// Update all the sidebar lines
updateSidebarLines(
"",
"<white>Name:</white> <yellow>${player.name}</yellow>",
"<white>Level: </white> <yellow>${player.level}</yellow>",
"",
"<white>Deaths:</white> <yellow>${player.getStatistic(Statistic.DEATHS)}</yellow>",
"<white>Kills:</white> <yellow>${player.getStatistic(Statistic.PLAYER_KILLS)}</yellow>",
""
)
}
scoreboards[player] = scoreboard
}
```

If you want this to be Dynamic so the values will auto update for example, you would need to use the method `updateSidebarLines` in a BukkitRunnable.

**BELOW NAME**

The Below Name display is perfect for showing health, stats, or other player-specific information under their name.
Let's create a Dynamic BelowName that shows the player health.

```kotlin
// Keep track of all scoreboards with a Map
private val scoreboards = mutableMapOf<Player, TwilightScoreboard>()

val join = event<PlayerJoinEvent> {
// Same as the sidebar, create an instance when a player joins
val scoreboard = TwilightScoreboard(player)

scoreboard.apply {
// Set up below name display
belowName("<white>Health")
updateBelowNameScore(player, player.health.toInt())
}

// Update the scoreboard for all players so everyone sees the same health
plugin.server.onlinePlayers.forEach { onlinePlayer ->
scoreboard.updateBelowNameScore(onlinePlayer, onlinePlayer.health.toInt())
scoreboards[onlinePlayer]?.updateBelowNameScore(player, player.health.toInt())
}
scoreboards[player] = scoreboard
}

// Update health on damage
val damage = event<EntityDamageEvent> {
if (entity !is Player) return@event
val player = entity as Player
// Update for all players
plugin.server.onlinePlayers.forEach { onlinePlayer ->
scoreboards[onlinePlayer]?.updateBelowNameScore(player, player.health.toInt())
}
}

// Update health when it increases
val regen = event<EntityRegainHealthEvent> {
if (entity !is Player) return@event
val player = entity as Player
// Update for all players
plugin.server.onlinePlayers.forEach { onlinePlayer ->
scoreboards[onlinePlayer]?.updateBelowNameScore(player, player.health.toInt())
}
}
```

**Tab List (Player List Header/Footer)**

Pretty straightforward, lets you customize the TabList of your server with just one method.
Like the sidebar, this can be made dynamic by simply having a BukkitRunnable that updates the TabList lines every 20 ticks (1 second).

```kotlin
scoreboard.updateTabList(
header = {
"""
<blue><bold>Welcome to the Server!</bold></blue>
<aqua>${player.name}</aqua>
<white>Enjoy your stay!</white>
""".trimIndent()
},
footer = {
"""
<gray>Players online: ${plugin.server.onlinePlayers.size}</gray>
<yellow>play.server.com</yellow>
""".trimIndent()
}
)
```

**Player Prefixes and Suffixes**

Add custom prefixes or suffixes to players!
*Note: You would need like the Below Name to update the prefix/suffix for everyone when you change it.*
```kotlin
// Apply a prefix to a player
scoreboard.prefix(player, "<red><bold>[ADMIN]</bold></red> ")

// Apply a suffix to a player
scoreboard.suffix(player, " <gray>[AFK]</gray>")
```

**Suggestion**

Not removing players from the Map when they quit can cause Memory Leaks,
so it's best to clean up everything when they quit to be safe!

```kotlin
val quit = event<PlayerQuitEvent> {
scoreboards[player]?.delete()
scoreboards.remove(player)
}
```

### GUI Builder
Creating GUI's can be an incredibly long and tedious process, however, Twilight offers a clean and efficient way.
Expand Down Expand Up @@ -426,7 +564,7 @@ db.connect()
```

#### QueryBuilder
The QueryBuilder class will help you in creating everything from simple queries like SELECTs to even complex JOINs.
The QueryBuilder class will help you in creating everything from simple queries like SELECTs to even complex JOINs.

All you need to start is an instance of `QueryBuilder`. Here's an example usage:
```kotlin
Expand All @@ -448,7 +586,7 @@ val deleteQuery = queryBuilder.delete().table("person").where("id = 1").build()
If you would like to retrieve and store data as objects within your database there a some methods provided for this


1 - Your object must implement SQLSerializable
1 - Your object must implement SQLSerializable

2 - You must have a table that fits the structure of your object, you can create by calling `convertToSQLTable()` on your object, then execute the statement like so:
```kotlin
Expand Down Expand Up @@ -478,7 +616,7 @@ val result = result.executeQuery(selectQuery)
Once you have run the query it will return a `Results` class, it can be used like so:
```kotlin
result?.let { res ->
println("MyColumn Value: " + res["my_column"])
println("MyColumn Value: " + res["my_column"])
}
```
The results class contains a list of all the rows and columns returned by the database.
Expand Down Expand Up @@ -508,9 +646,9 @@ Similarly, if you have a name and want to get the UUID, you can call `uuidFromNa
NameCacheService.uuidFromName("stxphen")
```

Currently, the only way to configure your MongoDB "cache" for UUIDs and names, is to have an Environment variable called `NAME_CACHE_COLLECTION` with the value being what you want to call the collection.
Currently, the only way to configure your MongoDB "cache" for UUIDs and names, is to have an Environment variable called `NAME_CACHE_COLLECTION` with the value being what you want to call the collection.

Don't want to use the Mongo cache? Disable `useMongoCache` in the settings.
Don't want to use the Mongo cache? Disable `useMongoCache` in the settings.

# Redis
Twilight has a Redis system that lets you set/get/delete string key value pairs, additionally, you can publish messages and listen to incoming messages on any channel you'd like.
Expand Down Expand Up @@ -586,9 +724,9 @@ Redis.set("cool-key", "super-secret-value")
val future = Redis.get("cool-key") // Returns a Completable Future

future.thenApplyAsync {
value -> println("The value is: $value") // Prints: "The value is: super-secret-value"
value -> println("The value is: $value") // Prints: "The value is: super-secret-value"
}.exceptionally {
e -> println("An exception occurred: ${e.message}") // Handle the Exception
e -> println("An exception occurred: ${e.message}") // Handle the Exception
}

Thread.sleep(1000)
Expand Down Expand Up @@ -661,4 +799,4 @@ We're aiming to provide some standard GSON Type Adapters for ease of use. Curren

We have a useful exclusion strategy, allowing you to exclude marked fields of a class from being in serialized JSON. All you need to do is annotate the class field with `@Exclude`!

Make sure to use our `GSON` import rather than making your own/own builder, as the Gson instance has to declare the ExclusionStrategy.
Make sure to use our `GSON` import rather than making your own/own builder, as the Gson instance has to declare the ExclusionStrategy.
6 changes: 4 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group = "gg.flyte"
version = "1.1.17"
version = "1.1.18"

repositories {
mavenLocal()
Expand Down Expand Up @@ -76,4 +76,6 @@ publishing {
}
}
}
}
}


99 changes: 99 additions & 0 deletions src/main/kotlin/gg/flyte/twilight/scoreboard/TwilightScoreboard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package gg.flyte.twilight.scoreboard

import gg.flyte.twilight.string.toMini
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.scoreboard.DisplaySlot
import org.bukkit.scoreboard.Objective
import org.bukkit.scoreboard.Scoreboard
import org.bukkit.scoreboard.Team

class TwilightScoreboard(private val player: Player) {
private var scoreboard: Scoreboard = Bukkit.getScoreboardManager().newScoreboard
private var sidebarObjective: Objective? = null
private var belowNameObjective: Objective? = null
private val teams = mutableMapOf<String, Team>()

init {
player.scoreboard = scoreboard
}

fun updateSidebarTitle(title: String) {
if (sidebarObjective == null) {
sidebarObjective = scoreboard.registerNewObjective("sidebar", "dummy", title.toMini())
sidebarObjective?.displaySlot = DisplaySlot.SIDEBAR
} else {
sidebarObjective?.displayName(title.toMini())
}
}

fun updateSidebarLines(vararg lines: String) {
if (sidebarObjective == null) return

scoreboard.entries.forEach { entry ->
if (entry.startsWith(" ")) {
scoreboard.resetScores(entry)
}
}

lines.forEachIndexed { index, line ->
val score = lines.size - index
val entry = " ".repeat(score)

val team = teams.getOrPut("line_$index") {
scoreboard.getTeam("line_$index") ?: scoreboard.registerNewTeam("line_$index").apply {
addEntry(entry)
}
}

team.prefix(line.toMini())
sidebarObjective?.getScore(entry)?.score = score
}
}

fun updateTabList(header: () -> String, footer: () -> String) {
player.sendPlayerListHeaderAndFooter(
header().toMini(),
footer().toMini()
)
}

fun belowName(title: String) {
if (belowNameObjective == null) {
belowNameObjective = scoreboard.registerNewObjective("belowname", "dummy", title.toMini())
belowNameObjective?.displaySlot = DisplaySlot.BELOW_NAME
} else {
belowNameObjective?.displayName(title.toMini())
}
}

fun updateBelowNameScore(target: Player, score: Int) { belowNameObjective?.getScore(target.name)?.score = score }

fun prefix(target: Player, prefix: String) {
val teamName = "prefix_${target.uniqueId}"
val team = teams.getOrPut(teamName) {
scoreboard.getTeam(teamName) ?: scoreboard.registerNewTeam(teamName)
}
team.prefix(prefix.toMini())
team.addEntry(target.name)
}

fun suffix(target: Player, suffix: String) {
val teamName = "prefix_${target.uniqueId}"
val team = teams.getOrPut(teamName) {
scoreboard.getTeam(teamName) ?: scoreboard.registerNewTeam(teamName)
}
team.suffix(suffix.toMini())
team.addEntry(target.name)
}

fun player(): Player = player

fun delete() {
player.scoreboard = Bukkit.getScoreboardManager().mainScoreboard
sidebarObjective?.unregister()
belowNameObjective?.unregister()
teams.values.forEach { it.unregister() }
teams.clear()
}
}
Loading