forked from Upstream/Velocitab
refactor: internals refactor, fix logic, new configs, spectator fix (#138)
* Started refactor * more work * Bug fixes and more work * Fixed task problem * More work on providers + fixed relocation problem * Added providers + relocated snakeyaml * Fixed relocation problem + removed org.json * maps instantiation refactored * Fixed reload problem * Fixed logic problem * More work on refactoring PlayerTabList * Using lombok for procteded values * More work * Fixed cache problem + more work on refactor * Fix for https://github.com/WiIIiam278/Velocitab/issues/35 * fixed conversations * Code refactor * Fixed problem while using minimessage * Added more javadocs and removed kick handling as velocity fixed that problem * Added username_lower placeholder and removed useless libraries * Updated docs * Added option to remove spectator effect in tablist
This commit is contained in:
parent
08501e84b8
commit
89a1f7add3
17
build.gradle
17
build.gradle
@ -21,6 +21,7 @@ ext {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
|
||||
maven { url = 'https://repo.papermc.io/repository/maven-public/' }
|
||||
maven { url = 'https://repo.william278.net/releases/' }
|
||||
maven { url = 'https://repo.william278.net/velocity/' }
|
||||
@ -31,9 +32,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly ('com.velocitypowered:velocity-api:3.3.0-SNAPSHOT') {
|
||||
exclude group: 'net.kyori'
|
||||
}
|
||||
compileOnly 'com.velocitypowered:velocity-api:3.3.0-SNAPSHOT'
|
||||
compileOnly 'com.velocitypowered:velocity-proxy:3.3.0-SNAPSHOT'
|
||||
compileOnly 'io.netty:netty-codec-http:4.1.103.Final'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.30'
|
||||
@ -42,18 +41,12 @@ dependencies {
|
||||
compileOnly 'net.william278:PAPIProxyBridge:1.4.2'
|
||||
compileOnly 'it.unimi.dsi:fastutil:8.5.12'
|
||||
compileOnly 'net.kyori:adventure-nbt:4.14.0'
|
||||
compileOnly 'net.kyori:adventure-api:4.14.0'
|
||||
compileOnly 'net.kyori:adventure-text-minimessage:4.14.0'
|
||||
compileOnly 'net.kyori:adventure-text-serializer-legacy:4.14.0'
|
||||
compileOnly 'net.kyori:adventure-text-serializer-gson:4.14.0'
|
||||
|
||||
|
||||
implementation 'org.apache.commons:commons-text:1.11.0'
|
||||
implementation 'net.william278:annotaml:2.0.7'
|
||||
implementation 'net.william278:DesertWell:2.0.4'
|
||||
implementation 'dev.dejvokep:boosted-yaml:1.3.1'
|
||||
implementation 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
||||
implementation 'org.bstats:bstats-velocity:3.0.2'
|
||||
implementation 'com.github.Exlll.ConfigLib:configlib-yaml:v4.3.0'
|
||||
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.30'
|
||||
}
|
||||
@ -94,14 +87,14 @@ shadowJar {
|
||||
relocate 'org.jetbrains', 'net.william278.velocitab.libraries'
|
||||
relocate 'org.intellij', 'net.william278.velocitab.libraries'
|
||||
relocate 'de.themoep', 'net.william278.velocitab.libraries'
|
||||
relocate 'dev.dejvokep.boostedyaml', 'net.william278.velocitab.libraries.boostedyaml'
|
||||
relocate 'net.william278.annotaml', 'net.william278.velocitab.libraries.annotaml'
|
||||
relocate 'net.william278.desertwell', 'net.william278.velocitab.libraries.desertwell'
|
||||
relocate 'org.json', 'net.william278.velocitab.libraries.json'
|
||||
relocate 'org.bstats', 'net.william278.velocitab.libraries.bstats'
|
||||
relocate 'de.exlll.configlib', 'net.william278.velocitab.libraries.configlib'
|
||||
|
||||
dependencies {
|
||||
exclude dependency(':slf4j-api')
|
||||
exclude dependency('org.json:json')
|
||||
}
|
||||
|
||||
destinationDirectory.set(file("$rootDir/target"))
|
||||
|
@ -11,17 +11,14 @@ To add additional frames of animation to a header format for a [server group](se
|
||||
|
||||
```yaml
|
||||
headers:
|
||||
default:
|
||||
- '&rainbow&Running Velocitab by William278'
|
||||
- '&rainbow:10&Running Velocitab by William278'
|
||||
- '&rainbow:20&Running Velocitab by William278'
|
||||
```
|
||||
</details>
|
||||
|
||||
By default, the plugin will switch between each frame whenever it is updated. To get this to animate, you must configure your `update_rate` setting.
|
||||
|
||||
### Setting the frame rate
|
||||
The `update_rate` setting in your `config.yml` file—set to `0` by default—controls the length (in milliseconds†) between your TAB list being updated. On each update, the header or footer format will use the next frame in the list, looping back to the first after the last one has been displayed.
|
||||
The `header_footer_update_rate` setting in your `tab_groups.yml` (different for each group) file—set to `0` by default—controls the length (in milliseconds†) between your TAB list being updated. On each update, the header or footer format will use the next frame in the list, looping back to the first after the last one has been displayed.
|
||||
|
||||
A good starting value for this could be `1000`, which is equivalent to one second. Once you've changed the value, use `/velocitab reload` to update the TAB menu in-game without restarting your proxy. Note the minimum update rate is `200` to avoid excessive network packet traffic, so values between `1`-`199` will be rounded up to `200`. If this value is set to `0` or below (as it is by default), the TAB menu will only update when a player joins or leaves, permissions are recalculated on LuckPerms, or the proxy is reloaded.
|
||||
|
||||
@ -35,10 +32,9 @@ Wondering how to make something like the above example? Here's how! This example
|
||||
<details>
|
||||
<summary>Example rainbow fade (config.yml)</summary>
|
||||
|
||||
Please note this is not a complete config file; you will need to add the relevant sections to the correct part in your own Velocitab `config.yml`.
|
||||
Please note this is not a complete tab_groups file; you will need to add the relevant sections to the correct part in your own Velocitab `tab_groups.yml`.
|
||||
```yaml
|
||||
headers:
|
||||
default:
|
||||
- '&rainbow&Velocitab ⭐ A super-simple (sorted!) Velocity TAB menu plugin\n'
|
||||
- '&rainbow:2&Velocitab ⭐ A super-simple (sorted!) Velocity TAB menu plugin\n'
|
||||
- '&rainbow:4&Velocitab ⭐ A super-simple (sorted!) Velocity TAB menu plugin\n'
|
||||
@ -71,18 +67,15 @@ headers:
|
||||
- '&rainbow:58&Velocitab ⭐ A super-simple (sorted!) Velocity TAB menu plugin\n'
|
||||
- '&rainbow:60&Velocitab ⭐ A super-simple (sorted!) Velocity TAB menu plugin\n'
|
||||
footers:
|
||||
default:
|
||||
- |
|
||||
\n&7For Velocity proxy servers:
|
||||
bd96a-#6cffa9&https://modrinth.com/plugin/velocitab
|
||||
bd96a-#6cffa9&https://william278.net/project/veloictab'
|
||||
formats:
|
||||
default: 'ϧ-#fff&[%server%] &f%username%'
|
||||
formatting_type: MINEDOWN
|
||||
server_groups:
|
||||
default:
|
||||
- server
|
||||
- server2
|
||||
update_rate: 200
|
||||
format: 'ϧ-#fff&[%server%] &f%username%'
|
||||
header_footer_update_rate: 200
|
||||
```
|
||||
In config.yml
|
||||
```yaml
|
||||
formatter: MINEDOWN
|
||||
```
|
||||
</details>
|
||||
|
@ -1,4 +1,5 @@
|
||||
This page contains the configuration file reference for Velocitab. The config file is located in `/plugins/velocitab/config.yml`
|
||||
This page contains configuration file references for Velocitab.
|
||||
The config file is located in `/plugins/velocitab/config.yml` and the tab groups file is located in `/plugins/velocitab/tab_groups.yml`
|
||||
|
||||
## Example config
|
||||
<details>
|
||||
@ -9,27 +10,15 @@ This page contains the configuration file reference for Velocitab. The config fi
|
||||
# ┃ Velocitab Config ┃
|
||||
# ┃ Developed by William278 ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
# ┗╸ Placeholders: %players_online%, %max_players_online%, %local_players_online%, %current_date%, %current_time%, %username%, %server%, %ping%, %prefix%, %suffix%, %role%
|
||||
# Header(s) to display above the TAB list for each server group.
|
||||
# List multiple headers and set update_rate to the number of ticks between frames for basic animations
|
||||
headers:
|
||||
default:
|
||||
- '&rainbow&Running Velocitab by William278'
|
||||
# Footer(s) to display below the TAB list for each server group, same as headers.
|
||||
footers:
|
||||
default:
|
||||
- '[There are currently %players_online%/%max_players_online% players online](gray)'
|
||||
formats:
|
||||
default: '&7[%server%] &f%prefix%%username%'
|
||||
# ┣╸ Information: https://william278.net/project/velocitab
|
||||
# ┗╸ Documentation: https://william278.net/docs/velocitab
|
||||
|
||||
# Check for updates on startup
|
||||
check_for_updates: true
|
||||
# Whether to remove nametag from players' heads if the nametag associated with their server group is empty.
|
||||
remove_nametags: false
|
||||
remove_nametags: true
|
||||
# Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY)
|
||||
formatting_type: MINEDOWN
|
||||
# The servers in each group of servers
|
||||
server_groups:
|
||||
default:
|
||||
- server
|
||||
- server2
|
||||
formatter: MINEDOWN
|
||||
# All servers which are not in other groups will be put in the fallback group.
|
||||
# "false" will exclude them from Velocitab.
|
||||
fallback_enabled: true
|
||||
@ -44,18 +33,57 @@ server_display_names:
|
||||
# Whether to enable the PAPIProxyBridge hook for PAPI support
|
||||
enable_papi_hook: true
|
||||
# How long in seconds to cache PAPI placeholders for, in milliseconds. (0 to disable)
|
||||
papi_cache_time: 30000
|
||||
papi_cache_time: 200
|
||||
# If you are using MINIMESSAGE formatting, enable this to support MiniPlaceholders in formatting.
|
||||
enable_miniplaceholders_hook: true
|
||||
enable_mini_placeholders_hook: true
|
||||
# Whether to send scoreboard teams packets. Required for player list sorting and nametag formatting.
|
||||
# Turn this off if you're using scoreboard teams on backend servers.
|
||||
send_scoreboard_packets: true
|
||||
# Whether to sort players in the TAB list.
|
||||
sort_players: true
|
||||
# Ordered list of elements by which players should be sorted. (Correct values are both internal placeholders and, if enabled, PAPI placeholders)
|
||||
sorting_placeholders:
|
||||
- %role_weight%
|
||||
- %username%
|
||||
# How often in milliseconds to periodically update the TAB list, including header and footer, for all users.
|
||||
# If set to 0, TAB will be updated on player join/leave instead. (1s = 1000ms)
|
||||
update_rate: 0
|
||||
# Remove gamemode spectator effect for other players in the TAB list.
|
||||
remove_spectator_effect: false
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Example tab groups
|
||||
|
||||
<details>
|
||||
|
||||
<summary>tab_groups.yml</summary>
|
||||
|
||||
```yaml
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ Velocitab TabGroups ┃
|
||||
# ┃ Developed by William278 ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
# ┣╸ Information: https://william278.net/project/velocitab
|
||||
# ┗╸ Documentation: https://william278.net/docs/velocitab
|
||||
|
||||
groups:
|
||||
- name: default
|
||||
headers:
|
||||
- '&rainbow&Running Velocitab by William278'
|
||||
footers:
|
||||
- '[There are currently %players_online%/%max_players_online% players online](gray)'
|
||||
format: '&7[%server%] &f%prefix%%username%'
|
||||
nametag:
|
||||
prefix: '&f%prefix%'
|
||||
suffix: '&f%suffix%'
|
||||
servers:
|
||||
- lobby
|
||||
- survival
|
||||
- creative
|
||||
- minigames
|
||||
- skyblock
|
||||
- prison
|
||||
- hub
|
||||
sorting_placeholders:
|
||||
- '%role_weight%'
|
||||
- '%username_lower%'
|
||||
header_footer_update_rate: 1000
|
||||
placeholder_update_rate: 1000
|
||||
```
|
||||
|
||||
</details>
|
||||
|
@ -1,14 +1,14 @@
|
||||
Velocitab supports the full range of modern color formatting, including RGB colors and gradients. Both MineDown (_default_), MiniMessage and Legacy formatting are supported. To change which formatter is being used, change the `formatting_type` value in `config.yml` to `MINEDOWN`, `MINIMESSAGE` or `LEGACY` respectively.
|
||||
Velocitab supports the full range of modern color formatting, including RGB colors and gradients. Both MineDown (_default_), MiniMessage and Legacy formatting are supported. To change which formatter is being used, change the `formatter` value in `config.yml` to `MINEDOWN`, `MINIMESSAGE` or `LEGACY` respectively.
|
||||
|
||||
Formatting is applied on header, footer and player text for each server group, and is applied after [[Placeholders]] have been inserted.
|
||||
|
||||
## MineDown syntax reference
|
||||
MineDown is the default formatter type, enabled by setting `formatting_type` to `MINEDOWN` in `config.yml`. See the [MineDown Syntax Reference](https://github.com/Phoenix616/MineDown) on GitHub for the specification of how to format text with it.
|
||||
MineDown is the default formatter type, enabled by setting `formatter` to `MINEDOWN` in `config.yml`. See the [MineDown Syntax Reference](https://github.com/Phoenix616/MineDown) on GitHub for the specification of how to format text with it.
|
||||
|
||||
## MiniMessage syntax reference
|
||||
MiniMessage formatting can be enabled by setting `formatting_type` to `MINIMESSAGE` in `config.yml`. See the [MiniMessage Syntax Reference](https://docs.advntr.dev/minimessage/format.html) on the Adventure Docs for how to format text with it. Using MiniMessage as the formatter also allows compatibility for using MiniPlaceholders in text.
|
||||
MiniMessage formatting can be enabled by setting `formatter` to `MINIMESSAGE` in `config.yml`. See the [MiniMessage Syntax Reference](https://docs.advntr.dev/minimessage/format.html) on the Adventure Docs for how to format text with it. Using MiniMessage as the formatter also allows compatibility for using MiniPlaceholders in text.
|
||||
|
||||
## Legacy formatting
|
||||
> **Warning:** The option for legacy formatting is provided only for backwards compatibility with other plugins. Please consider using the MineDown or MiniMessage options instead!
|
||||
|
||||
Legacy formatting can be enabled by setting `formatting_type` to `LEGACY` in `config.yml`. Legacy formatter supports Mojang color and formatting codes (e.g. `&d`, `&l`), Adventure-styled RGB color codes (e.g. `&#a25981`), as well as BungeeCord RGB color codes (e.g. `&x&a&2&5&9&8&1`). See the [LegacyComponentSerializer Syntax Reference](https://docs.advntr.dev/serializer/legacy.html) on the Adventure Docs for more technical details.
|
||||
Legacy formatting can be enabled by setting `formatter` to `LEGACY` in `config.yml`. Legacy formatter supports Mojang color and formatting codes (e.g. `&d`, `&l`), Adventure-styled RGB color codes (e.g. `&#a25981`), as well as BungeeCord RGB color codes (e.g. `&x&a&2&5&9&8&1`). See the [LegacyComponentSerializer Syntax Reference](https://docs.advntr.dev/serializer/legacy.html) on the Adventure Docs for more technical details.
|
||||
|
@ -8,13 +8,14 @@ Velocitab supports formatting the nametags of players (the text displayed above
|
||||
You can configure nametags per-group using the `nametags` section of the config file. Each group should have one nametag format associated with it, which will be applied to all players on servers in that group.
|
||||
|
||||
<details>
|
||||
<summary>Editing nametags (config.yml)</summary>
|
||||
<summary>Editing nametags (tab_groups.yml)</summary>
|
||||
|
||||
```yaml
|
||||
# Nametag(s) to display above players' heads for each server group. Set to empty to disable.
|
||||
# Nametag formats must contain a %username%. Docs: https://william278.net/docs/velocitab/nametags
|
||||
nametags:
|
||||
default: '&f%prefix%%username%&f%suffix%'
|
||||
nametag:
|
||||
prefix: '&f%prefix%'
|
||||
suffix: '&f%suffix%'
|
||||
|
||||
# (...)
|
||||
|
||||
@ -27,11 +28,9 @@ send_scoreboard_packets: true
|
||||
Only players on servers which are part of groups that specify nametag formats will have their nametag formatted. To disable nametag formatting, remove all groups from the `nametags` section of the config file (leaving it empty).
|
||||
|
||||
## Removing name tags
|
||||
In order to remove nametags, you must remove your nametag format from the config file. If you want to remove nametags for all groups, you can set the `nametags` section to empty `nametags: {}`. After that be sure to set `remove_nametags` to `true` to make sure the nametags are removed from players.
|
||||
In order to remove nametags, you must set `prefix` and `suffix` to empty. After that be sure to set `remove_nametags` to `true` in the [`config.yml` file](config-file).
|
||||
|
||||
## Formatting limitations
|
||||
Nametags must adhere to the following restrictions:
|
||||
* A %username% placeholder must be present. This is used for delimiting the scoreboard prefix, name, and suffix to facilitate formatting.
|
||||
* Only legacy colors can be used in formats. If RGB colors are specified, they will automatically be downsampled to the nearest legacy color. This is a limitation of the scoreboard team system.
|
||||
* Nametags cannot contain newlines (must be on a single line)
|
||||
* Gradients are not supported.
|
||||
* Only legacy colors can be used in username formats. If RGB colors are specified, they will automatically be downsampled to the nearest legacy color. This is a limitation of the scoreboard team system.
|
||||
* Nametags cannot contain newlines (must be on a single line).
|
@ -11,6 +11,7 @@ Placeholders can be included in the header, footer and player name format of the
|
||||
| `%current_date%` | Current real-world date of the server | `24 Feb 2023` |
|
||||
| `%current_time%` | Current real-world time of the server | `21:45:32` |
|
||||
| `%username%` | The player's username | `William278` |
|
||||
| `%username_lower%` | The player's username, in lowercase | `william278` |
|
||||
| `%server%` | Name of the server the player is on | `alpha` |
|
||||
| `%ping%` | Ping of the player (in ms) | `6` |
|
||||
| `%prefix%` | The player's prefix (from LuckPerms) | `&4[Admin]` |
|
||||
|
@ -1,95 +1,198 @@
|
||||
Velocitab supports defining multiple server groups, each providing distinct formatting for players in the TAB list, alongside unique headers and footers. This is useful if you wish to display different information in TAB depending on the server a player is on. You can also set formatting to use for [[Nametags]] above players' heads per-group.
|
||||
Velocitab supports defining multiple server groups, each providing distinct formatting for players in the TAB list,
|
||||
alongside unique headers and footers. This is useful if you wish to display different information in TAB depending on
|
||||
the server a player is on. You can also set formatting to use for [[Nametags]] above players' heads per-group.
|
||||
|
||||
## Defining groups
|
||||
Groups are defined in the `server_groups` section of `config.yml`, as a list of servers following the group name (by default, a group `default` will be present, alongside a list of servers on your network.
|
||||
|
||||
Groups are defined in `tab_groups.yml`, as a list of TabGroup elements.
|
||||
|
||||
Every group must have a unique name, and a list of servers to include in the group. You can also define a list of
|
||||
sorting placeholders to use when sorting players in the TAB list, and a header/footer update rate and placeholder update
|
||||
rate to use for the group.
|
||||
|
||||
## Headers and footers
|
||||
|
||||
<details>
|
||||
<summary>Example of a default config.yml</summary>
|
||||
<summary>Example of headers and footers</summary>
|
||||
|
||||
```yaml
|
||||
server_groups:
|
||||
default:
|
||||
- lobby1
|
||||
- lobby2
|
||||
- lobby3
|
||||
headers:
|
||||
- '&rainbow&Running Velocitab by William278'
|
||||
footers:
|
||||
- '[There are currently %players_online%/%max_players_online% players online](gray)'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
You can define as many groups as you wish in this section by adding more lists of servers.
|
||||
You can define a list of headers and footers to use for each group. These will be cycled through at the rate defined
|
||||
by `header_footer_update_rate` in milliseconds. If you only want to use one header/footer, you can define a single
|
||||
element list. You can also use the `|` character to define a multi-line header/footer. See [[Animations]] for more
|
||||
information.
|
||||
|
||||
## Formats
|
||||
|
||||
<details>
|
||||
<summary>Example of format</summary>
|
||||
|
||||
```yaml
|
||||
format: '&7[%server%] &f%prefix%%username%'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
You can define a format to use for each group. This will be used to format the text of each player in the TAB list.
|
||||
See [[Formatting]] for more information.
|
||||
Player formats may only utilize one line.
|
||||
|
||||
## Nametags
|
||||
|
||||
<details>
|
||||
<summary>Example of nametag</summary>
|
||||
|
||||
```yaml
|
||||
nametag:
|
||||
prefix: '&f%prefix%'
|
||||
suffix: '&f%suffix%'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
You can define a nametag to use for each group. This will be used to format the text above each player's head.
|
||||
See [[Nametags]] for more information.
|
||||
Player nametags may only utilize one line.
|
||||
|
||||
## Servers
|
||||
|
||||
<details>
|
||||
<summary>Example of servers</summary>
|
||||
|
||||
```yaml
|
||||
servers:
|
||||
- lobby
|
||||
- survival
|
||||
- creative
|
||||
- minigames
|
||||
- skyblock
|
||||
- prison
|
||||
- hub
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
You can define a list of servers to include in each group.
|
||||
|
||||
## Sorting placeholders
|
||||
|
||||
<details>
|
||||
<summary>Example of sorting placeholders</summary>
|
||||
|
||||
```yaml
|
||||
sorting_placeholders:
|
||||
- '%role_weight%'
|
||||
- '%username_lower%'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
You can define a list of sorting placeholders to use when sorting players in the TAB list. See [[Sorting]] for more
|
||||
information.
|
||||
|
||||
## Header/footer update rate
|
||||
|
||||
<details>
|
||||
<summary>Example of header/footer update rate</summary>
|
||||
|
||||
```yaml
|
||||
header_footer_update_rate: 1000
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
You can define a header/footer update rate to use for each group, in milliseconds. This will determine how quickly the
|
||||
headers and footers will cycle through in the TAB list. The default is 1000 milliseconds (1 second).
|
||||
|
||||
## Placeholder update rate
|
||||
|
||||
<details>
|
||||
<summary>Example of placeholder update rate</summary>
|
||||
|
||||
```yaml
|
||||
placeholder_update_rate: 1000
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
You can define a placeholder update rate to use for each group, in milliseconds. This will determine how quickly the
|
||||
placeholders in the TAB list will update. The default is 1000 milliseconds (1 second).
|
||||
|
||||
|
||||
## Example tab groups
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Adding more groups</summary>
|
||||
|
||||
```yaml
|
||||
server_groups:
|
||||
lobbies:
|
||||
- lobby1
|
||||
- lobby2
|
||||
creative:
|
||||
- creative_lobby
|
||||
- creative1
|
||||
survival:
|
||||
- survival1
|
||||
- survival2
|
||||
```
|
||||
groups:
|
||||
- name: lobbies
|
||||
headers:
|
||||
- '&rainbow&Running Velocitab by William278 on Lobbies!'
|
||||
footers:
|
||||
- '[There are currently %players_online%/%max_players_online% players online](gray)'
|
||||
format: '&7[%server%] &f%prefix%%username%'
|
||||
servers:
|
||||
- lobby
|
||||
- hub
|
||||
- minigames
|
||||
- creative
|
||||
- survival
|
||||
sorting_placeholders:
|
||||
- '%role_weight%'
|
||||
- '%username_lower%'
|
||||
header_footer_update_rate: 1000
|
||||
placeholder_update_rate: 1000
|
||||
- name: creative
|
||||
headers:
|
||||
- '&rainbow&Running Velocitab by William278 on Creative!'
|
||||
footers:
|
||||
- '[There are currently %players_online%/%max_players_online% players online](gray)'
|
||||
format: '&7[%server%] &f%prefix%%username%'
|
||||
servers:
|
||||
- creative
|
||||
sorting_placeholders:
|
||||
- '%role_weight%'
|
||||
- '%username_lower%'
|
||||
header_footer_update_rate: 1000
|
||||
placeholder_update_rate: 1000
|
||||
- name: survival
|
||||
headers:
|
||||
- '&rainbow&Running Velocitab by William278 on Survival!'
|
||||
footers:
|
||||
- '[There are currently %players_online%/%max_players_online% players online](gray)'
|
||||
format: '&7[%server%] &f%prefix%%username%'
|
||||
servers:
|
||||
- survival
|
||||
sorting_placeholders:
|
||||
- '%role_weight%'
|
||||
- '%username_lower%'
|
||||
header_footer_update_rate: 1000
|
||||
placeholder_update_rate: 1000
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Mapping headers, footers, user formats, and nametags to groups
|
||||
Once you've defined your groups, you can modify the `headers`, `footers`, `formats` and [`nametags`](nametags) section of the file with different formats for each group.
|
||||
|
||||
<details>
|
||||
<summary>Per-group formats</summary>
|
||||
|
||||
```yaml
|
||||
headers:
|
||||
lobbies:
|
||||
- 'Welcome, %username%! Join a server to start!'
|
||||
creative:
|
||||
- '%username% is playing Creative!'
|
||||
survival:
|
||||
- '%username% is playing Survival!'
|
||||
footers:
|
||||
lobbies:
|
||||
- 'There are %players_online%players online!'
|
||||
creative:
|
||||
- 'Currently connected to a creative server: %server%!'
|
||||
survival:
|
||||
- 'Today is %current_date%!'
|
||||
formats:
|
||||
lobbies: '&8[Lobby] &7%username%'
|
||||
creative: '&e[Creative] &7[%server%] &f%prefix%%username%'
|
||||
survival: '&2[Survival (%server%)] &f%prefix%%username%'
|
||||
nametags:
|
||||
lobbies: '&8[Lobby] &7%prefix%%username%&7%suffix%'
|
||||
creative: '&e%prefix%%username%&7%suffix%'
|
||||
survival: '&7%prefix%%username%&7%suffix%'
|
||||
```
|
||||
</details>
|
||||
|
||||
See [[Placeholders]] for how to use placeholders in these formats, and [[Formatting]] for how to format text with colors, and see [[Animations]] for how to create basic animations by adding more headers/footers to each group's list. Note that some formatting limitations apply to nametags — [[Nametags]] for more information.
|
||||
|
||||
### Adding new lines
|
||||
If you want to add a new line to your header or footer format, you can use `\n` to insert one — but since this gets messy quickly, there's an easier way using the YAML markup pipe character to declare a multiline string:
|
||||
|
||||
<details>
|
||||
<summary>Multi-line headers/footers</summary>
|
||||
|
||||
```yaml
|
||||
footers:
|
||||
lobbies:
|
||||
- |
|
||||
There are %players_online%players online!
|
||||
I'm a second line
|
||||
Third line, woohoo~!
|
||||
```
|
||||
</details>
|
||||
|
||||
Player name formats and nametags may only utilize one line.
|
||||
See [[Placeholders]] for how to use placeholders in these formats, and [[Formatting]] for how to format text with
|
||||
colors, and see [[Animations]] for how to create basic animations by adding more headers/footers to each group's list.
|
||||
Note that some formatting limitations apply to nametags — [[Nametags]] for more information.
|
||||
|
||||
## Default group
|
||||
If a player isn't connected to a server on your network, their TAB menu will be formatted as per the formats defined by `fallback_group` set in `config.yml`, provided `fallback_enabled` is set to `true`.
|
||||
|
||||
If you don't want them to have their TAB handled at all by Velocitab, you can use this to disable Velocitab formatting on certain servers altogether by disabling the `fallback_enabled` setting and excluding servers you do not wish to format from being part of a group.
|
||||
If a player isn't connected to a server on your network, their TAB menu will be formatted as per the formats defined
|
||||
by `fallback_group` set in `config.yml`, provided `fallback_enabled` is set to `true`.
|
||||
|
||||
If you don't want them to have their TAB handled at all by Velocitab, you can use this to disable Velocitab formatting
|
||||
on certain servers altogether by disabling the `fallback_enabled` setting and excluding servers you do not wish to
|
||||
format from being part of a group.
|
||||
|
||||
<details>
|
||||
<summary>Example in config.yml</summary>
|
||||
@ -101,4 +204,5 @@ fallback_enabled: true
|
||||
# The formats to use for the fallback group.
|
||||
fallback_group: 'lobbies'
|
||||
```
|
||||
|
||||
</details>
|
@ -3,6 +3,6 @@ javaVersion=17
|
||||
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||
org.gradle.daemon=true
|
||||
|
||||
plugin_version=1.5.2
|
||||
plugin_version=1.6
|
||||
plugin_archive=velocitab
|
||||
plugin_description=A beautiful and versatile TAB list plugin for Velocity proxies
|
||||
|
@ -31,69 +31,75 @@ import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||
import lombok.Getter;
|
||||
import net.william278.annotaml.Annotaml;
|
||||
import lombok.Setter;
|
||||
import net.william278.desertwell.util.UpdateChecker;
|
||||
import net.william278.desertwell.util.Version;
|
||||
import net.william278.velocitab.api.VelocitabAPI;
|
||||
import net.william278.velocitab.commands.VelocitabCommand;
|
||||
import net.william278.velocitab.config.ConfigProvider;
|
||||
import net.william278.velocitab.config.Formatter;
|
||||
import net.william278.velocitab.config.Settings;
|
||||
import net.william278.velocitab.config.TabGroups;
|
||||
import net.william278.velocitab.hook.Hook;
|
||||
import net.william278.velocitab.hook.LuckPermsHook;
|
||||
import net.william278.velocitab.hook.MiniPlaceholdersHook;
|
||||
import net.william278.velocitab.hook.PAPIProxyBridgeHook;
|
||||
import net.william278.velocitab.packet.ScoreboardManager;
|
||||
import net.william278.velocitab.packet.PacketEventManager;
|
||||
import net.william278.velocitab.sorting.SortingManager;
|
||||
import net.william278.velocitab.tab.PlayerTabList;
|
||||
import net.william278.velocitab.providers.HookProvider;
|
||||
import net.william278.velocitab.providers.LoggerProvider;
|
||||
import net.william278.velocitab.providers.MetricProvider;
|
||||
import net.william278.velocitab.providers.ScoreboardProvider;
|
||||
import net.william278.velocitab.vanish.VanishManager;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bstats.velocity.Metrics;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Plugin(id = "velocitab")
|
||||
public class Velocitab {
|
||||
private static final int METRICS_ID = 18247;
|
||||
@SuppressWarnings("unused")
|
||||
@Getter
|
||||
public class Velocitab implements ConfigProvider, ScoreboardProvider, LoggerProvider, HookProvider, MetricProvider {
|
||||
@Setter
|
||||
private Settings settings;
|
||||
@Setter
|
||||
private TabGroups tabGroups;
|
||||
private final ProxyServer server;
|
||||
private final Logger logger;
|
||||
private final Path dataDirectory;
|
||||
private final Path configDirectory;
|
||||
@Inject
|
||||
private PluginContainer pluginContainer;
|
||||
@Inject
|
||||
private Metrics.Factory metricsFactory;
|
||||
@Setter
|
||||
private PlayerTabList tabList;
|
||||
@Setter
|
||||
private List<Hook> hooks;
|
||||
@Setter
|
||||
private ScoreboardManager scoreboardManager;
|
||||
@Setter
|
||||
private SortingManager sortingManager;
|
||||
@Getter
|
||||
private VanishManager vanishManager;
|
||||
private PacketEventManager packetEventManager;
|
||||
|
||||
@Inject
|
||||
public Velocitab(@NotNull ProxyServer server, @NotNull Logger logger, @DataDirectory Path dataDirectory) {
|
||||
public Velocitab(@NotNull ProxyServer server, @NotNull Logger logger, @DataDirectory Path configDirectory) {
|
||||
this.server = server;
|
||||
this.logger = logger;
|
||||
this.dataDirectory = dataDirectory;
|
||||
this.configDirectory = configDirectory;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyInitialization(@NotNull ProxyInitializeEvent event) {
|
||||
loadSettings();
|
||||
loadConfigs();
|
||||
loadHooks();
|
||||
prepareVanishManager();
|
||||
prepareScoreboardManager();
|
||||
prepareTabList();
|
||||
prepareSortingManager();
|
||||
prepareChannelManager();
|
||||
prepareScoreboard();
|
||||
registerCommands();
|
||||
registerMetrics();
|
||||
checkForUpdates();
|
||||
@ -105,99 +111,32 @@ public class Velocitab {
|
||||
public void onProxyShutdown(@NotNull ProxyShutdownEvent event) {
|
||||
server.getScheduler().tasksByPlugin(this).forEach(ScheduledTask::cancel);
|
||||
disableScoreboardManager();
|
||||
disableTabList();
|
||||
getLuckPermsHook().ifPresent(LuckPermsHook::closeEvent);
|
||||
VelocitabAPI.unregister();
|
||||
logger.info("Successfully disabled Velocitab");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ProxyServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Settings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Formatter getFormatter() {
|
||||
return getSettings().getFormatter();
|
||||
}
|
||||
|
||||
public void loadSettings() {
|
||||
try {
|
||||
settings = Annotaml.create(
|
||||
new File(dataDirectory.toFile(), "config.yml"),
|
||||
new Settings(this)
|
||||
).get();
|
||||
|
||||
settings.getNametags().values().stream()
|
||||
.filter(nametag -> !nametag.contains("%username%")).forEach(nametag -> {
|
||||
logger.warn("Nametag '" + nametag + "' does not contain %username% - removing");
|
||||
settings.getNametags().remove(nametag);
|
||||
});
|
||||
} catch (IOException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||
logger.error("Failed to load config file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private <H extends Hook> Optional<H> getHook(@NotNull Class<H> hookType) {
|
||||
return hooks.stream()
|
||||
.filter(hook -> hook.getClass().equals(hookType))
|
||||
.map(hookType::cast)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public Optional<LuckPermsHook> getLuckPermsHook() {
|
||||
return getHook(LuckPermsHook.class);
|
||||
}
|
||||
|
||||
public Optional<PAPIProxyBridgeHook> getPAPIProxyBridgeHook() {
|
||||
return getHook(PAPIProxyBridgeHook.class);
|
||||
}
|
||||
|
||||
public Optional<MiniPlaceholdersHook> getMiniPlaceholdersHook() {
|
||||
return getHook(MiniPlaceholdersHook.class);
|
||||
}
|
||||
|
||||
private void loadHooks() {
|
||||
this.hooks = new ArrayList<>();
|
||||
Hook.AVAILABLE.forEach(availableHook -> availableHook.apply(this).ifPresent(hooks::add));
|
||||
}
|
||||
|
||||
private void prepareScoreboardManager() {
|
||||
if (settings.isSendScoreboardPackets()) {
|
||||
this.scoreboardManager = new ScoreboardManager(this);
|
||||
scoreboardManager.registerPacket();
|
||||
}
|
||||
}
|
||||
|
||||
private void disableScoreboardManager() {
|
||||
if (scoreboardManager != null && settings.isSendScoreboardPackets()) {
|
||||
scoreboardManager.close();
|
||||
scoreboardManager.unregisterPacket();
|
||||
}
|
||||
}
|
||||
|
||||
private void disableTabList() {
|
||||
if (tabList != null) {
|
||||
tabList.close();
|
||||
}
|
||||
public void loadConfigs() {
|
||||
loadSettings();
|
||||
loadTabGroups();
|
||||
}
|
||||
|
||||
private void prepareVanishManager() {
|
||||
this.vanishManager = new VanishManager(this);
|
||||
}
|
||||
|
||||
private void prepareSortingManager() {
|
||||
this.sortingManager = new SortingManager(this);
|
||||
private void prepareChannelManager() {
|
||||
this.packetEventManager = new PacketEventManager(this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public SortingManager getSortingManager() {
|
||||
return sortingManager;
|
||||
@Override
|
||||
public Velocitab getPlugin() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -205,18 +144,6 @@ public class Velocitab {
|
||||
return Optional.ofNullable(scoreboardManager);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PlayerTabList getTabList() {
|
||||
return tabList;
|
||||
}
|
||||
|
||||
private void prepareTabList() {
|
||||
this.tabList = new PlayerTabList(this);
|
||||
server.getEventManager().register(this, tabList);
|
||||
|
||||
server.getScheduler().buildTask(this, tabList::load).delay(1, TimeUnit.SECONDS).schedule();
|
||||
}
|
||||
|
||||
private void prepareAPI() {
|
||||
VelocitabAPI.register(this);
|
||||
}
|
||||
@ -239,16 +166,6 @@ public class Velocitab {
|
||||
return Version.fromString(getDescription().getVersion().orElseThrow(), "-");
|
||||
}
|
||||
|
||||
private void registerMetrics() {
|
||||
final Metrics metrics = metricsFactory.make(this, METRICS_ID);
|
||||
metrics.addCustomChart(new SimplePie("sort_players", () -> settings.isSortPlayers() ? "Enabled" : "Disabled"));
|
||||
metrics.addCustomChart(new SimplePie("formatter_type", () -> settings.getFormatter().getName()));
|
||||
metrics.addCustomChart(new SimplePie("using_luckperms", () -> getLuckPermsHook().isPresent() ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie("using_papiproxybridge", () -> getPAPIProxyBridgeHook().isPresent() ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie("using_miniplaceholders", () -> getMiniPlaceholdersHook().isPresent() ? "Yes" : "No"));
|
||||
|
||||
}
|
||||
|
||||
private void checkForUpdates() {
|
||||
if (!getSettings().isCheckForUpdates()) {
|
||||
return;
|
||||
@ -269,28 +186,4 @@ public class Velocitab {
|
||||
.build();
|
||||
}
|
||||
|
||||
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... exceptions) {
|
||||
switch (level) {
|
||||
case ERROR -> {
|
||||
if (exceptions.length > 0) {
|
||||
logger.error(message, exceptions[0]);
|
||||
} else {
|
||||
logger.error(message);
|
||||
}
|
||||
}
|
||||
case WARN -> {
|
||||
if (exceptions.length > 0) {
|
||||
logger.warn(message, exceptions[0]);
|
||||
} else {
|
||||
logger.warn(message);
|
||||
}
|
||||
}
|
||||
case INFO -> logger.info(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void log(@NotNull String message) {
|
||||
this.log(Level.INFO, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,54 +19,11 @@
|
||||
|
||||
package net.william278.velocitab.api;
|
||||
|
||||
import net.william278.velocitab.config.Group;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class PlayerAddedToTabEvent {
|
||||
public record PlayerAddedToTabEvent(@NotNull TabPlayer player, @NotNull Group group) {
|
||||
|
||||
private final TabPlayer player;
|
||||
private final String group;
|
||||
private final List<String> groupServers;
|
||||
|
||||
public PlayerAddedToTabEvent(@NotNull TabPlayer player, @NotNull String group, @NotNull List<String> groupServers) {
|
||||
this.player = player;
|
||||
this.group = group;
|
||||
this.groupServers = groupServers;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TabPlayer getTabPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getGroup() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getGroupServers() {
|
||||
return this.groupServers;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated(forRemoval = true)
|
||||
public TabPlayer player() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated(forRemoval = true)
|
||||
public String group() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Deprecated(forRemoval = true)
|
||||
public List<String> groupServers() {
|
||||
return this.groupServers;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ package net.william278.velocitab.api;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.config.Group;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import net.william278.velocitab.tab.PlayerTabList;
|
||||
import net.william278.velocitab.vanish.VanishIntegration;
|
||||
@ -168,11 +169,11 @@ public class VelocitabAPI {
|
||||
*
|
||||
* @param player the player for whom to retrieve the server group
|
||||
* @return the name of the server group that the player is connected to,
|
||||
* or an empty string if the player is not in a group server
|
||||
* or a null value if the player is not connected to a server group
|
||||
*/
|
||||
@NotNull
|
||||
public String getServerGroup(@NotNull Player player) {
|
||||
return getUser(player).map(t -> t.getServerGroup(plugin)).orElse("");
|
||||
@Nullable
|
||||
public Group getServerGroup(@NotNull Player player) {
|
||||
return getUser(player).map(TabPlayer::getGroup).orElse(null);
|
||||
}
|
||||
|
||||
|
||||
|
@ -101,7 +101,7 @@ public final class VelocitabCommand {
|
||||
.then(LiteralArgumentBuilder.<CommandSource>literal("reload")
|
||||
.requires(src -> src.hasPermission("velocitab.command.reload"))
|
||||
.executes(ctx -> {
|
||||
plugin.loadSettings();
|
||||
plugin.loadConfigs();
|
||||
plugin.getTabList().reloadUpdate();
|
||||
ctx.getSource().sendMessage(Component.text(
|
||||
"Velocitab has been reloaded!",
|
||||
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.config;
|
||||
|
||||
import de.exlll.configlib.NameFormatters;
|
||||
import de.exlll.configlib.YamlConfigurationProperties;
|
||||
import de.exlll.configlib.YamlConfigurations;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Interface for getting and setting data from plugin configuration files
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface ConfigProvider {
|
||||
|
||||
@NotNull
|
||||
YamlConfigurationProperties.Builder<?> YAML_CONFIGURATION_PROPERTIES = YamlConfigurationProperties.newBuilder()
|
||||
.setNameFormatter(NameFormatters.LOWER_UNDERSCORE);
|
||||
|
||||
/**
|
||||
* Get the plugin settings, read from the config file
|
||||
*
|
||||
* @return the plugin settings
|
||||
* @since 1.0
|
||||
*/
|
||||
@NotNull
|
||||
Settings getSettings();
|
||||
|
||||
/**
|
||||
* Set the plugin settings
|
||||
*
|
||||
* @param settings The settings to set
|
||||
* @since 1.0
|
||||
*/
|
||||
void setSettings(@NotNull Settings settings);
|
||||
|
||||
/**
|
||||
* Load the plugin settings from the config file
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
default void loadSettings() {
|
||||
setSettings(YamlConfigurations.update(
|
||||
getConfigDirectory().resolve("config.yml"),
|
||||
Settings.class,
|
||||
YAML_CONFIGURATION_PROPERTIES.header(Settings.CONFIG_HEADER).build()
|
||||
));
|
||||
getSettings().validateConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tab groups
|
||||
*
|
||||
* @return the tab groups
|
||||
* @since 1.0
|
||||
*/
|
||||
@NotNull
|
||||
TabGroups getTabGroups();
|
||||
|
||||
/**
|
||||
* Set the tab groups
|
||||
*
|
||||
* @param tabGroups The tab groups to set
|
||||
* @since 1.0
|
||||
*/
|
||||
void setTabGroups(@NotNull TabGroups tabGroups);
|
||||
|
||||
/**
|
||||
* Load the tab groups from the config file
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
default void loadTabGroups() {
|
||||
setTabGroups(YamlConfigurations.update(
|
||||
getConfigDirectory().resolve("tab_groups.yml"),
|
||||
TabGroups.class,
|
||||
YAML_CONFIGURATION_PROPERTIES.header(TabGroups.CONFIG_HEADER).build()
|
||||
));
|
||||
getTabGroups().validateConfig();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the plugin config directory
|
||||
*
|
||||
* @return the plugin config directory
|
||||
* @since 1.0
|
||||
*/
|
||||
@NotNull
|
||||
Path getConfigDirectory();
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.config;
|
||||
|
||||
public interface ConfigValidator {
|
||||
|
||||
/**
|
||||
* Validates the configuration settings.
|
||||
* @throws IllegalStateException if the configuration is invalid
|
||||
*/
|
||||
void validateConfig() throws IllegalStateException;
|
||||
|
||||
}
|
@ -38,19 +38,22 @@ public enum Formatter {
|
||||
MINEDOWN(
|
||||
(text, player, plugin) -> new MineDown(text).toComponent(),
|
||||
(text) -> text.replace("__", "_\\_"),
|
||||
"MineDown"
|
||||
"MineDown",
|
||||
(text) -> new MineDown(text).toComponent()
|
||||
),
|
||||
MINIMESSAGE(
|
||||
(text, player, plugin) -> plugin.getMiniPlaceholdersHook()
|
||||
.map(hook -> hook.format(text, player.getPlayer()))
|
||||
.orElse(MiniMessage.miniMessage().deserialize(text)),
|
||||
(text) -> MiniMessage.miniMessage().escapeTags(text),
|
||||
"MiniMessage"
|
||||
"MiniMessage",
|
||||
(text) -> MiniMessage.miniMessage().deserialize(text)
|
||||
),
|
||||
LEGACY(
|
||||
(text, player, plugin) -> LegacyComponentSerializer.legacyAmpersand().deserialize(text),
|
||||
Function.identity(),
|
||||
"Legacy Text"
|
||||
"Legacy Text",
|
||||
(text) -> LegacyComponentSerializer.legacyAmpersand().deserialize(text)
|
||||
);
|
||||
|
||||
/**
|
||||
@ -66,12 +69,14 @@ public enum Formatter {
|
||||
* Function to escape formatting characters in a string
|
||||
*/
|
||||
private final Function<String, String> escaper;
|
||||
private final Function<String, Component> emptyFormatter;
|
||||
|
||||
Formatter(@NotNull TriFunction<String, TabPlayer, Velocitab, Component> formatter, @NotNull Function<String, String> escaper,
|
||||
@NotNull String name) {
|
||||
@NotNull String name, @NotNull Function<String, Component> emptyFormatter) {
|
||||
this.formatter = formatter;
|
||||
this.escaper = escaper;
|
||||
this.name = name;
|
||||
this.emptyFormatter = emptyFormatter;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -85,6 +90,11 @@ public enum Formatter {
|
||||
.serialize(format(text, player, plugin));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Component emptyFormat(@NotNull String text) {
|
||||
return emptyFormatter.apply(text);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String escape(@NotNull String text) {
|
||||
return escaper.apply(text);
|
||||
|
104
src/main/java/net/william278/velocitab/config/Group.java
Normal file
104
src/main/java/net/william278/velocitab/config/Group.java
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.config;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import de.exlll.configlib.Comment;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import net.william278.velocitab.tab.Nametag;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public record Group(
|
||||
String name,
|
||||
List<String> headers,
|
||||
List<String> footers,
|
||||
String format,
|
||||
Nametag nametag,
|
||||
List<String> servers,
|
||||
List<String> sortingPlaceholders,
|
||||
@Comment("""
|
||||
How often in milliseconds to periodically update the TAB list, including header and footer, for all users.
|
||||
If set to 0, TAB will be updated on player join/leave instead. (1s = 1000ms)
|
||||
The minimal update rate is 200ms, anything lower will automatically be set to 200ms.""")
|
||||
int headerFooterUpdateRate,
|
||||
int placeholderUpdateRate
|
||||
) {
|
||||
|
||||
@NotNull
|
||||
public String getHeader(int index) {
|
||||
return headers.isEmpty() ? "" : StringEscapeUtils.unescapeJava(headers
|
||||
.get(Math.max(0, Math.min(index, headers.size() - 1))));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getFooter(int index) {
|
||||
return footers.isEmpty() ? "" : StringEscapeUtils.unescapeJava(footers
|
||||
.get(Math.max(0, Math.min(index, footers.size() - 1))));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<RegisteredServer> registeredServers(Velocitab plugin) {
|
||||
if (isDefault() && plugin.getSettings().isFallbackEnabled()) {
|
||||
return new ArrayList<>(plugin.getServer().getAllServers());
|
||||
}
|
||||
return servers.stream()
|
||||
.map(plugin.getServer()::getServer)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return name.equals("default");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Player> getPlayers(Velocitab plugin) {
|
||||
List<Player> players = new ArrayList<>();
|
||||
for (RegisteredServer server : registeredServers(plugin)) {
|
||||
players.addAll(server.getPlayersConnected());
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<TabPlayer> getTabPlayers(Velocitab plugin) {
|
||||
return plugin.getTabList().getPlayers()
|
||||
.values()
|
||||
.stream()
|
||||
.filter(tabPlayer -> tabPlayer.getGroup().equals(this))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Group group)) {
|
||||
return false;
|
||||
}
|
||||
return name.equals(group.name);
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import net.william278.velocitab.tab.Nametag;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.function.TriFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -47,6 +48,7 @@ public enum Placeholder {
|
||||
CURRENT_DATE((plugin, player) -> DateTimeFormatter.ofPattern("dd MMM yyyy").format(LocalDateTime.now())),
|
||||
CURRENT_TIME((plugin, player) -> DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now())),
|
||||
USERNAME((plugin, player) -> player.getCustomName().orElse(player.getPlayer().getUsername())),
|
||||
USERNAME_LOWER((plugin, player) -> player.getCustomName().orElse(player.getPlayer().getUsername()).toLowerCase()),
|
||||
SERVER((plugin, player) -> player.getServerDisplayName(plugin)),
|
||||
PING((plugin, player) -> Long.toString(player.getPlayer().getPing())),
|
||||
PREFIX((plugin, player) -> player.getRole().getPrefix().orElse("")),
|
||||
@ -54,7 +56,7 @@ public enum Placeholder {
|
||||
ROLE((plugin, player) -> player.getRole().getName().orElse("")),
|
||||
ROLE_DISPLAY_NAME((plugin, player) -> player.getRole().getDisplayName().orElse("")),
|
||||
ROLE_WEIGHT((plugin, player) -> player.getRoleWeightString()),
|
||||
SERVER_GROUP((plugin, player) -> player.getServerGroup(plugin)),
|
||||
SERVER_GROUP((plugin, player) -> player.getGroup().name()),
|
||||
SERVER_GROUP_INDEX((plugin, player) -> Integer.toString(player.getServerGroupPosition(plugin))),
|
||||
DEBUG_TEAM_NAME((plugin, player) -> plugin.getFormatter().escape(player.getLastTeamName().orElse(""))),
|
||||
LUCKPERMS_META_((param, plugin, player) -> plugin.getLuckPermsHook()
|
||||
@ -68,6 +70,7 @@ public enum Placeholder {
|
||||
private final boolean parameterised;
|
||||
private final Pattern pattern;
|
||||
private final static Pattern checkPlaceholders = Pattern.compile("%.*?%");
|
||||
private final static String DELIMITER = ":::";
|
||||
|
||||
Placeholder(@NotNull BiFunction<Velocitab, TabPlayer, String> replacer) {
|
||||
this.parameterised = false;
|
||||
@ -81,9 +84,20 @@ public enum Placeholder {
|
||||
this.pattern = Pattern.compile("%" + this.name().toLowerCase() + "[^%]+%", Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
public static CompletableFuture<Nametag> replace(@NotNull Nametag nametag, @NotNull Velocitab plugin,
|
||||
@NotNull TabPlayer player) {
|
||||
return replace(nametag.prefix() + DELIMITER + nametag.suffix(), plugin, player)
|
||||
.thenApply(s -> s.split(DELIMITER, 2))
|
||||
.thenApply(v -> new Nametag(v[0], v.length > 1 ? v[1] : ""));
|
||||
}
|
||||
|
||||
public static CompletableFuture<String> replace(@NotNull String format, @NotNull Velocitab plugin,
|
||||
@NotNull TabPlayer player) {
|
||||
|
||||
if (format.equals(DELIMITER)) {
|
||||
return CompletableFuture.completedFuture("");
|
||||
}
|
||||
|
||||
for (Placeholder placeholder : values()) {
|
||||
Matcher matcher = placeholder.pattern.matcher(format);
|
||||
if (placeholder.parameterised) {
|
||||
|
@ -19,185 +19,72 @@
|
||||
|
||||
package net.william278.velocitab.config;
|
||||
|
||||
import de.exlll.configlib.Comment;
|
||||
import de.exlll.configlib.Configuration;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import net.william278.annotaml.YamlComment;
|
||||
import net.william278.annotaml.YamlFile;
|
||||
import net.william278.annotaml.YamlKey;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@YamlFile(header = """
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Velocitab Config ┃
|
||||
┃ Developed by William278 ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
┣╸ Information: https://william278.net/project/velocitab
|
||||
┗╸ Documentation: https://william278.net/docs/velocitab""")
|
||||
public class Settings {
|
||||
|
||||
@Getter
|
||||
@YamlKey("check_for_updates")
|
||||
@YamlComment("Check for updates on startup")
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@Getter
|
||||
@Configuration
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Settings implements ConfigValidator{
|
||||
|
||||
public static final String CONFIG_HEADER = """
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Velocitab Config ┃
|
||||
┃ Developed by William278 ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
┣╸ Information: https://william278.net/project/velocitab
|
||||
┗╸ Documentation: https://william278.net/docs/velocitab""";
|
||||
|
||||
@Comment("Check for updates on startup")
|
||||
private boolean checkForUpdates = true;
|
||||
|
||||
@YamlKey("headers")
|
||||
@YamlComment("Header(s) to display above the TAB list for each server group."
|
||||
+ "\nList multiple headers and set update_rate to the number of ticks between frames for basic animations")
|
||||
private Map<String, List<String>> headers = Map.of(
|
||||
"default",
|
||||
List.of("&rainbow&Running Velocitab by William278")
|
||||
);
|
||||
|
||||
@YamlKey("footers")
|
||||
@YamlComment("Footer(s) to display below the TAB list for each server group, same as headers.")
|
||||
private Map<String, List<String>> footers = Map.of(
|
||||
"default",
|
||||
List.of("[There are currently %players_online%/%max_players_online% players online](gray)")
|
||||
);
|
||||
|
||||
@YamlKey("formats")
|
||||
private Map<String, String> formats = Map.of("default", "&7[%server%] &f%prefix%%username%");
|
||||
|
||||
@Getter
|
||||
@YamlKey("nametags")
|
||||
@YamlComment("Nametag(s) to display above players' heads for each server group. Set to empty to disable."
|
||||
+ "\nNametag formats must contain a %username%. Docs: https://william278.net/docs/velocitab/nametags")
|
||||
private Map<String, String> nametags = Map.of("default", "&f%prefix%%username%&f%suffix%");
|
||||
|
||||
@Getter
|
||||
@YamlKey("remove_nametags")
|
||||
@YamlComment("Whether to remove nametag from players' heads if the nametag associated with their server group is empty.")
|
||||
@Comment("Whether to remove nametag from players' heads if the nametag associated with their server group is empty.")
|
||||
private boolean removeNametags = false;
|
||||
|
||||
@Getter
|
||||
@YamlComment("Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY)")
|
||||
@YamlKey("formatting_type")
|
||||
@Comment("Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY)")
|
||||
private Formatter formatter = Formatter.MINEDOWN;
|
||||
|
||||
@Getter
|
||||
@YamlKey("server_groups")
|
||||
@YamlComment("The servers in each group of servers. The order of groups is important when sorting by SERVER_GROUP.")
|
||||
private LinkedHashMap<String, List<String>> serverGroups = new LinkedHashMap<>(Map.of(
|
||||
"default",
|
||||
List.of("lobby1", "lobby2", "lobby3"))
|
||||
);
|
||||
|
||||
@Getter
|
||||
@YamlKey("fallback_enabled")
|
||||
@YamlComment("All servers which are not in other groups will be put in the fallback group."
|
||||
@Comment("All servers which are not in other groups will be put in the fallback group."
|
||||
+ "\n\"false\" will exclude them from Velocitab.")
|
||||
private boolean fallbackEnabled = true;
|
||||
|
||||
@Getter
|
||||
@YamlKey("fallback_group")
|
||||
@YamlComment("The formats to use for the fallback group.")
|
||||
@Comment("The formats to use for the fallback group.")
|
||||
private String fallbackGroup = "default";
|
||||
|
||||
@Getter
|
||||
@YamlKey("only_list_players_in_same_group")
|
||||
@YamlComment("Only show other players on a server that is part of the same server group as the player.")
|
||||
@Comment("Only show other players on a server that is part of the same server group as the player.")
|
||||
private boolean onlyListPlayersInSameGroup = true;
|
||||
|
||||
@Getter
|
||||
@YamlKey("server_display_names")
|
||||
@YamlComment("Define custom names to be shown in the TAB list for specific server names."
|
||||
@Comment("Define custom names to be shown in the TAB list for specific server names."
|
||||
+ "\nIf no custom display name is provided for a server, its original name will be used.")
|
||||
private Map<String, String> serverDisplayNames = Map.of("very-long-server-name", "VLSN");
|
||||
|
||||
@Getter
|
||||
@YamlKey("enable_papi_hook")
|
||||
@YamlComment("Whether to enable the PAPIProxyBridge hook for PAPI support")
|
||||
@Comment("Whether to enable the PAPIProxyBridge hook for PAPI support")
|
||||
private boolean enablePapiHook = true;
|
||||
|
||||
@Getter
|
||||
@YamlKey("papi_cache_time")
|
||||
@YamlComment("How long in seconds to cache PAPI placeholders for, in milliseconds. (0 to disable)")
|
||||
@Comment("How long in seconds to cache PAPI placeholders for, in milliseconds. (0 to disable)")
|
||||
private long papiCacheTime = 30000;
|
||||
|
||||
@Getter
|
||||
@YamlKey("enable_miniplaceholders_hook")
|
||||
@YamlComment("If you are using MINIMESSAGE formatting, enable this to support MiniPlaceholders in formatting.")
|
||||
@Comment("If you are using MINIMESSAGE formatting, enable this to support MiniPlaceholders in formatting.")
|
||||
private boolean enableMiniPlaceholdersHook = true;
|
||||
|
||||
@Getter
|
||||
@YamlKey("send_scoreboard_packets")
|
||||
@YamlComment("Whether to send scoreboard teams packets. Required for player list sorting and nametag formatting."
|
||||
@Comment("Whether to send scoreboard teams packets. Required for player list sorting and nametag formatting."
|
||||
+ "\nTurn this off if you're using scoreboard teams on backend servers.")
|
||||
private boolean sendScoreboardPackets = true;
|
||||
|
||||
@Getter
|
||||
@YamlKey("sort_players")
|
||||
@YamlComment("Whether to sort players in the TAB list.")
|
||||
@Comment("Whether to sort players in the TAB list.")
|
||||
private boolean sortPlayers = true;
|
||||
|
||||
@YamlKey("sorting_placeholders")
|
||||
@YamlComment("Ordered list of elements by which players should be sorted. " +
|
||||
"(Correct values are both internal placeholders and, if enabled, PAPI placeholders)")
|
||||
private List<String> sortingPlaceholders = List.of(
|
||||
"%role_weight%",
|
||||
"%username%"
|
||||
);
|
||||
|
||||
@Getter
|
||||
@YamlKey("update_rate")
|
||||
@YamlComment("""
|
||||
How often in milliseconds to periodically update the TAB list, including header and footer, for all users.
|
||||
If set to 0, TAB will be updated on player join/leave instead. (1s = 1000ms)
|
||||
The minimal update rate is 200ms, anything lower will automatically be set to 200ms.""")
|
||||
private int updateRate = 0;
|
||||
|
||||
public Settings(@NotNull Velocitab plugin) {
|
||||
this.serverGroups = new LinkedHashMap<>(Map.of("default",
|
||||
plugin.getServer().getAllServers().stream().map(server -> server.getServerInfo().getName()).toList()
|
||||
));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public Settings() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getHeader(@NotNull String serverGroup, int index) {
|
||||
final List<String> groupHeaders = headers.getOrDefault(serverGroup, List.of(""));
|
||||
return groupHeaders.isEmpty() ? "" : StringEscapeUtils.unescapeJava(groupHeaders
|
||||
.get(Math.max(0, Math.min(index, getHeaderListSize(serverGroup) - 1))));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getFooter(@NotNull String serverGroup, int index) {
|
||||
final List<String> groupFooters = footers.getOrDefault(serverGroup, List.of(""));
|
||||
return groupFooters.isEmpty() ? "" : StringEscapeUtils.unescapeJava(groupFooters
|
||||
.get(Math.max(0, Math.min(index, getFooterListSize(serverGroup) - 1))));
|
||||
}
|
||||
|
||||
public int getHeaderListSize(@NotNull String serverGroup) {
|
||||
return headers.getOrDefault(serverGroup, List.of("")).size();
|
||||
}
|
||||
|
||||
public int getFooterListSize(@NotNull String serverGroup) {
|
||||
return footers.getOrDefault(serverGroup, List.of("")).size();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getFormat(@NotNull String serverGroup) {
|
||||
return StringEscapeUtils.unescapeJava(
|
||||
formats.getOrDefault(serverGroup, "%username%"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getNametag(@NotNull String serverGroup) {
|
||||
return StringEscapeUtils.unescapeJava(
|
||||
nametags.getOrDefault(serverGroup, ""));
|
||||
}
|
||||
|
||||
public boolean doNametags() {
|
||||
return !nametags.isEmpty();
|
||||
}
|
||||
@Comment("Remove gamemode spectator effect for other players in the TAB list.")
|
||||
private boolean removeSpectatorEffect = true;
|
||||
|
||||
/**
|
||||
* Get display name for the server
|
||||
@ -210,33 +97,8 @@ public class Settings {
|
||||
return serverDisplayNames.getOrDefault(serverName, serverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ordinal position of the server group
|
||||
*
|
||||
* @param serverGroupName The server group name
|
||||
* @return The ordinal position of the server group
|
||||
*/
|
||||
public int getServerGroupPosition(@NotNull String serverGroupName) {
|
||||
return List.copyOf(serverGroups.keySet()).indexOf(serverGroupName);
|
||||
}
|
||||
@Override
|
||||
public void validateConfig() {
|
||||
|
||||
/**
|
||||
* Get the server group that a server is in
|
||||
*
|
||||
* @param serverName The name of the server
|
||||
* @return The server group that the server is in, or "default" if the server is not in a group
|
||||
*/
|
||||
@NotNull
|
||||
public String getServerGroup(String serverName) {
|
||||
return serverGroups.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().contains(serverName)).findFirst()
|
||||
.map(Map.Entry::getKey)
|
||||
.orElse(fallbackGroup);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getSortingElements() {
|
||||
return sortingPlaceholders;
|
||||
}
|
||||
|
||||
}
|
||||
|
91
src/main/java/net/william278/velocitab/config/TabGroups.java
Normal file
91
src/main/java/net/william278/velocitab/config/TabGroups.java
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.config;
|
||||
|
||||
import de.exlll.configlib.Configuration;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.william278.velocitab.tab.Nametag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@Getter
|
||||
@Configuration
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class TabGroups implements ConfigValidator {
|
||||
|
||||
public static final String CONFIG_HEADER = """
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Velocitab TabGroups ┃
|
||||
┃ Developed by William278 ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
┣╸ Information: https://william278.net/project/velocitab
|
||||
┗╸ Documentation: https://william278.net/docs/velocitab""";
|
||||
|
||||
public List<Group> groups = List.of(
|
||||
new Group(
|
||||
"default",
|
||||
List.of("&rainbow&Running Velocitab by William278"),
|
||||
List.of("[There are currently %players_online%/%max_players_online% players online](gray)"),
|
||||
"&7[%server%] &f%prefix%%username%",
|
||||
new Nametag("&f%prefix%", "&f%suffix%"),
|
||||
List.of("lobby", "survival", "creative", "minigames", "skyblock", "prison", "hub"),
|
||||
List.of("%role_weight%", "%username_lower%"),
|
||||
1000,
|
||||
1000
|
||||
)
|
||||
);
|
||||
|
||||
@NotNull
|
||||
public Group getGroupFromName(@NotNull String name) {
|
||||
return groups.stream()
|
||||
.filter(group -> group.name().equals(name))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("No group with name " + name + " found"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Group getGroupFromServer(@NotNull String server) {
|
||||
for (Group group : groups) {
|
||||
if (group.servers().contains(server)) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return getGroupFromName("default");
|
||||
}
|
||||
|
||||
public int getPosition(@NotNull Group group) {
|
||||
return groups.indexOf(group) + 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void validateConfig() {
|
||||
if (groups.isEmpty()) {
|
||||
throw new IllegalStateException("No tab groups defined in config");
|
||||
}
|
||||
if (groups.stream().noneMatch(group -> group.name().equals("default"))) {
|
||||
throw new IllegalStateException("No default tab group defined in config");
|
||||
}
|
||||
}
|
||||
}
|
@ -105,7 +105,7 @@ public class LuckPermsHook extends Hook {
|
||||
final Role oldRole = tabPlayer.getRole();
|
||||
tabPlayer.setRole(getRoleFromMetadata(event.getData().getMetaData()));
|
||||
tabList.updatePlayerDisplayName(tabPlayer);
|
||||
tabList.recalculateVanishForPlayer(tabPlayer);
|
||||
tabList.getVanishTabList().recalculateVanishForPlayer(tabPlayer);
|
||||
checkRoleUpdate(tabPlayer, oldRole);
|
||||
})
|
||||
.delay(500, TimeUnit.MILLISECONDS)
|
||||
|
@ -34,6 +34,7 @@ public class PAPIProxyBridgeHook extends Hook {
|
||||
super(plugin);
|
||||
this.api = PlaceholderAPI.createInstance();
|
||||
this.api.setCacheExpiry(Math.max(0, plugin.getSettings().getPapiCacheTime()));
|
||||
this.api.setRequestTimeout(1500);
|
||||
}
|
||||
|
||||
public CompletableFuture<String> formatPlaceholders(@NotNull String input, @NotNull Player player) {
|
||||
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.packet;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.velocitypowered.api.event.AwaitingEventExecutor;
|
||||
import com.velocitypowered.api.event.EventTask;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.network.Connections;
|
||||
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PacketEventManager {
|
||||
|
||||
private static final String KEY = "velocitab";
|
||||
private static final String CITIZENS_PREFIX = "CIT";
|
||||
|
||||
private final Velocitab plugin;
|
||||
@Getter
|
||||
private final Set<UUID> velocitabEntries;
|
||||
|
||||
public PacketEventManager(@NotNull Velocitab plugin) {
|
||||
this.plugin = plugin;
|
||||
this.velocitabEntries = Sets.newConcurrentHashSet();
|
||||
this.loadPlayers();
|
||||
this.loadListeners();
|
||||
}
|
||||
|
||||
private void loadPlayers() {
|
||||
plugin.getServer().getAllPlayers().forEach(this::injectPlayer);
|
||||
}
|
||||
|
||||
private void loadListeners() {
|
||||
plugin.getServer().getEventManager().register(plugin, PostLoginEvent.class,
|
||||
(AwaitingEventExecutor<PostLoginEvent>) postLoginEvent -> EventTask.withContinuation(continuation -> {
|
||||
injectPlayer(postLoginEvent.getPlayer());
|
||||
continuation.resume();
|
||||
}));
|
||||
|
||||
plugin.getServer().getEventManager().register(plugin, DisconnectEvent.class,
|
||||
(AwaitingEventExecutor<DisconnectEvent>) disconnectEvent ->
|
||||
disconnectEvent.getLoginStatus() == DisconnectEvent.LoginStatus.CONFLICTING_LOGIN
|
||||
? null
|
||||
: EventTask.async(() -> removePlayer(disconnectEvent.getPlayer())));
|
||||
}
|
||||
|
||||
public void injectPlayer(@NotNull Player player) {
|
||||
final PlayerChannelHandler handler = new PlayerChannelHandler(plugin, player);
|
||||
final ConnectedPlayer connectedPlayer = (ConnectedPlayer) player;
|
||||
removePlayer(player);
|
||||
connectedPlayer.getConnection()
|
||||
.getChannel()
|
||||
.pipeline()
|
||||
.addBefore(Connections.HANDLER, KEY, handler);
|
||||
}
|
||||
|
||||
public void removePlayer(@NotNull Player player) {
|
||||
final ConnectedPlayer connectedPlayer = (ConnectedPlayer) player;
|
||||
final Channel channel = connectedPlayer.getConnection().getChannel();
|
||||
if (channel.pipeline().get(KEY) != null) {
|
||||
channel.pipeline().remove(KEY);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleEntry(@NotNull UpsertPlayerInfo packet, @NotNull Player player) {
|
||||
final List<TabPlayer> toUpdate = packet.getEntries().stream()
|
||||
.filter(entry -> entry.getProfile() != null)
|
||||
.filter(entry -> !entry.getProfile().getName().startsWith(CITIZENS_PREFIX))
|
||||
.filter(entry -> velocitabEntries.stream().noneMatch(uuid -> uuid.equals(entry.getProfile().getId())))
|
||||
.map(entry -> entry.getProfile().getId())
|
||||
.map(id -> plugin.getTabList().getTabPlayer(id))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.toList();
|
||||
|
||||
if (toUpdate.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
toUpdate.forEach(tabPlayer -> packet.getEntries().stream()
|
||||
.filter(entry -> entry.getProfile() != null)
|
||||
.filter(entry -> entry.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId()))
|
||||
.findFirst()
|
||||
.ifPresent(entry -> entry.setDisplayName(
|
||||
new ComponentHolder(player.getProtocolVersion(), tabPlayer.getLastDisplayname()))));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.packet;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PlayerChannelHandler extends ChannelDuplexHandler {
|
||||
|
||||
private final Velocitab plugin;
|
||||
private final Player player;
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
if (!(msg instanceof final UpsertPlayerInfo minecraftPacket)) {
|
||||
super.write(ctx, msg, promise);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (plugin.getSettings().isRemoveSpectatorEffect() && minecraftPacket.containsAction(UpsertPlayerInfo.Action.UPDATE_GAME_MODE)) {
|
||||
forceGameMode(minecraftPacket.getEntries());
|
||||
}
|
||||
|
||||
if (!minecraftPacket.containsAction(UpsertPlayerInfo.Action.ADD_PLAYER) && !minecraftPacket.containsAction(UpsertPlayerInfo.Action.UPDATE_LISTED)) {
|
||||
super.write(ctx, msg, promise);
|
||||
return;
|
||||
}
|
||||
|
||||
if (minecraftPacket.getEntries().stream().allMatch(entry -> entry.getProfile() != null && entry.getProfile().getName().startsWith("CIT"))) {
|
||||
super.write(ctx, msg, promise);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getPacketEventManager().handleEntry(minecraftPacket, player);
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
|
||||
private void forceGameMode(@NotNull List<UpsertPlayerInfo.Entry> entries) {
|
||||
entries.stream()
|
||||
.filter(entry -> entry.getProfileId() != null && entry.getGameMode() == 3 && !entry.getProfileId().equals(player.getUniqueId()))
|
||||
.forEach(entry -> entry.setGameMode(0));
|
||||
}
|
||||
}
|
@ -20,9 +20,12 @@
|
||||
package net.william278.velocitab.packet;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.nbt.BinaryTag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -40,7 +43,8 @@ public class Protocol765Adapter extends Protocol404Adapter {
|
||||
}
|
||||
|
||||
protected void writeComponent(ByteBuf buf, Component component) {
|
||||
new ComponentHolder(ProtocolVersion.MINECRAFT_1_20_3, component).write(buf);
|
||||
final BinaryTag tag = ComponentHolder.serialize(GsonComponentSerializer.gson().serializeToTree(component));
|
||||
ProtocolUtils.writeBinaryTag(buf, ProtocolVersion.MINECRAFT_1_20_3, tag);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
package net.william278.velocitab.packet;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
@ -28,11 +30,11 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import net.william278.velocitab.tab.Nametag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.*;
|
||||
|
||||
@ -42,13 +44,13 @@ public class ScoreboardManager {
|
||||
private final Velocitab plugin;
|
||||
private final Set<TeamsPacketAdapter> versions;
|
||||
private final Map<UUID, String> createdTeams;
|
||||
private final Map<String, TabPlayer.Nametag> nametags;
|
||||
private final Map<String, Nametag> nametags;
|
||||
|
||||
public ScoreboardManager(@NotNull Velocitab velocitab) {
|
||||
this.plugin = velocitab;
|
||||
this.createdTeams = new ConcurrentHashMap<>();
|
||||
this.nametags = new ConcurrentHashMap<>();
|
||||
this.versions = new HashSet<>();
|
||||
this.createdTeams = Maps.newConcurrentMap();
|
||||
this.nametags = Maps.newConcurrentMap();
|
||||
this.versions = Sets.newHashSet();
|
||||
this.registerVersions();
|
||||
}
|
||||
|
||||
@ -78,64 +80,37 @@ public class ScoreboardManager {
|
||||
public void resetCache(@NotNull Player player) {
|
||||
final String team = createdTeams.remove(player.getUniqueId());
|
||||
if (team != null) {
|
||||
dispatchGroupPacket(UpdateTeamsPacket.removeTeam(plugin, team), player);
|
||||
final TabPlayer tabPlayer = plugin.getTabList().getTabPlayer(player).orElseThrow();
|
||||
dispatchGroupPacket(UpdateTeamsPacket.removeTeam(plugin, team), tabPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
public void vanishPlayer(@NotNull Player player) {
|
||||
public void vanishPlayer(@NotNull TabPlayer tabPlayer) {
|
||||
this.handleVanish(tabPlayer, true);
|
||||
}
|
||||
|
||||
public void unVanishPlayer(@NotNull TabPlayer tabPlayer) {
|
||||
this.handleVanish(tabPlayer, false);
|
||||
}
|
||||
|
||||
private void handleVanish(@NotNull TabPlayer tabPlayer, boolean vanish) {
|
||||
if (!plugin.getSettings().isSortPlayers()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
|
||||
if (optionalServerConnection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RegisteredServer serverInfo = optionalServerConnection.get().getServer();
|
||||
final List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
|
||||
|
||||
final Player player = tabPlayer.getPlayer();
|
||||
final String teamName = createdTeams.get(player.getUniqueId());
|
||||
if (teamName == null) {
|
||||
return;
|
||||
}
|
||||
final List<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin);
|
||||
|
||||
final UpdateTeamsPacket packet = UpdateTeamsPacket.removeTeam(plugin, teamName);
|
||||
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> {
|
||||
final boolean canSee = plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername());
|
||||
|
||||
if (canSee) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchPacket(packet, connected);
|
||||
}));
|
||||
}
|
||||
|
||||
public void unVanishPlayer(@NotNull Player player) {
|
||||
if (!plugin.getSettings().isSortPlayers()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
|
||||
if (optionalServerConnection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RegisteredServer serverInfo = optionalServerConnection.get().getServer();
|
||||
final List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
|
||||
final String teamName = createdTeams.get(player.getUniqueId());
|
||||
if (teamName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<TabPlayer.Nametag> cachedTag = Optional.ofNullable(nametags.getOrDefault(teamName, null));
|
||||
final Optional<Nametag> cachedTag = Optional.ofNullable(nametags.getOrDefault(teamName, null));
|
||||
cachedTag.ifPresent(nametag -> {
|
||||
final UpdateTeamsPacket packet = UpdateTeamsPacket.create(
|
||||
plugin, createdTeams.get(player.getUniqueId()),
|
||||
nametag, player.getUsername()
|
||||
);
|
||||
final UpdateTeamsPacket packet = vanish ? UpdateTeamsPacket.removeTeam(plugin, teamName) :
|
||||
UpdateTeamsPacket.create(plugin, tabPlayer, teamName, nametag, player.getUsername());
|
||||
siblings.forEach(server -> server.getPlayersConnected().stream().filter(p -> p != player)
|
||||
.filter(p -> vanish && !plugin.getVanishManager().canSee(p.getUsername(), player.getUsername()))
|
||||
.forEach(connected -> dispatchPacket(packet, connected)));
|
||||
});
|
||||
}
|
||||
@ -143,38 +118,38 @@ public class ScoreboardManager {
|
||||
/**
|
||||
* Updates the role of the player in the scoreboard.
|
||||
*
|
||||
* @param player The player whose role will be updated. Must not be null.
|
||||
* @param role The new role of the player. Must not be null.
|
||||
* @param force Whether to force the update even if the player's nametag is the same.
|
||||
* @param tabPlayer The TabPlayer object representing the player whose role will be updated.
|
||||
* @param role The new role of the player. Must not be null.
|
||||
* @param force Whether to force the update even if the player's nametag is the same.
|
||||
*/
|
||||
public void updateRole(@NotNull Player player, @NotNull String role, boolean force) {
|
||||
public void updateRole(@NotNull TabPlayer tabPlayer, @NotNull String role, boolean force) {
|
||||
final Player player = tabPlayer.getPlayer();
|
||||
if (!player.isActive()) {
|
||||
plugin.getTabList().removeOfflinePlayer(player);
|
||||
return;
|
||||
}
|
||||
|
||||
final String name = player.getUsername();
|
||||
final TabPlayer tabPlayer = plugin.getTabList().getTabPlayer(player).orElseThrow();
|
||||
tabPlayer.getNametag(plugin).thenAccept(newTag -> {
|
||||
if (!createdTeams.getOrDefault(player.getUniqueId(), "").equals(role)) {
|
||||
if (createdTeams.containsKey(player.getUniqueId())) {
|
||||
dispatchGroupPacket(
|
||||
UpdateTeamsPacket.removeTeam(plugin, createdTeams.get(player.getUniqueId())),
|
||||
player
|
||||
tabPlayer
|
||||
);
|
||||
}
|
||||
|
||||
createdTeams.put(player.getUniqueId(), role);
|
||||
this.nametags.put(role, newTag);
|
||||
dispatchGroupPacket(
|
||||
UpdateTeamsPacket.create(plugin, role, newTag, name),
|
||||
player
|
||||
UpdateTeamsPacket.create(plugin, tabPlayer, role, newTag, name),
|
||||
tabPlayer
|
||||
);
|
||||
} else if (force || (this.nametags.containsKey(role) && !this.nametags.get(role).equals(newTag))) {
|
||||
this.nametags.put(role, newTag);
|
||||
dispatchGroupPacket(
|
||||
UpdateTeamsPacket.changeNametag(plugin, role, newTag),
|
||||
player
|
||||
UpdateTeamsPacket.changeNametag(plugin, tabPlayer, role, newTag),
|
||||
tabPlayer
|
||||
);
|
||||
}
|
||||
}).exceptionally(e -> {
|
||||
@ -184,18 +159,13 @@ public class ScoreboardManager {
|
||||
}
|
||||
|
||||
|
||||
public void resendAllTeams(@NotNull Player player) {
|
||||
public void resendAllTeams(@NotNull TabPlayer tabPlayer) {
|
||||
if (!plugin.getSettings().isSendScoreboardPackets()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
|
||||
if (optionalServerConnection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RegisteredServer serverInfo = optionalServerConnection.get().getServer();
|
||||
final List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
|
||||
final Player player = tabPlayer.getPlayer();
|
||||
final List<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin);
|
||||
final List<Player> players = siblings.stream()
|
||||
.map(RegisteredServer::getPlayersConnected)
|
||||
.flatMap(Collection::stream)
|
||||
@ -223,11 +193,10 @@ public class ScoreboardManager {
|
||||
roles.add(role);
|
||||
|
||||
// Send packet
|
||||
final TabPlayer.Nametag tag = nametags.get(role);
|
||||
final Nametag tag = nametags.get(role);
|
||||
if (tag != null) {
|
||||
final TabPlayer.Nametag nametag = nametags.get(role);
|
||||
final UpdateTeamsPacket packet = UpdateTeamsPacket.create(
|
||||
plugin, role, nametag, p.getUsername()
|
||||
plugin, tabPlayer, role, tag, p.getUsername()
|
||||
);
|
||||
dispatchPacket(packet, player);
|
||||
}
|
||||
@ -248,14 +217,14 @@ public class ScoreboardManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchGroupPacket(@NotNull UpdateTeamsPacket packet, @NotNull Player player) {
|
||||
private void dispatchGroupPacket(@NotNull UpdateTeamsPacket packet, @NotNull TabPlayer tabPlayer) {
|
||||
final Player player = tabPlayer.getPlayer();
|
||||
final Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
|
||||
if (optionalServerConnection.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RegisteredServer serverInfo = optionalServerConnection.get().getServer();
|
||||
final List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
|
||||
final List<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin);
|
||||
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> {
|
||||
try {
|
||||
final boolean canSee = plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername());
|
||||
@ -324,10 +293,10 @@ public class ScoreboardManager {
|
||||
dispatchPacket(removeTeam, player);
|
||||
|
||||
if (canSee) {
|
||||
final TabPlayer.Nametag tag = nametags.get(team);
|
||||
final Nametag tag = nametags.get(team);
|
||||
if (tag != null) {
|
||||
final UpdateTeamsPacket addTeam = UpdateTeamsPacket.create(
|
||||
plugin, team, tag, target.getPlayer().getUsername()
|
||||
plugin, tabPlayer, team, tag, target.getPlayer().getUsername()
|
||||
);
|
||||
dispatchPacket(addTeam, player);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import net.william278.velocitab.tab.Nametag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -64,8 +65,9 @@ public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected static UpdateTeamsPacket create(@NotNull Velocitab plugin, @NotNull String teamName,
|
||||
@NotNull TabPlayer.Nametag nametag,
|
||||
protected static UpdateTeamsPacket create(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer,
|
||||
@NotNull String teamName,
|
||||
@NotNull Nametag nametag,
|
||||
@NotNull String... teamMembers) {
|
||||
return new UpdateTeamsPacket(plugin)
|
||||
.teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName)
|
||||
@ -74,23 +76,24 @@ public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
|
||||
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
|
||||
.collisionRule(CollisionRule.ALWAYS)
|
||||
.color(getLastColor(nametag.getPrefix()))
|
||||
.prefix(nametag.getPrefixComponent(plugin))
|
||||
.suffix(nametag.getSuffixComponent(plugin))
|
||||
.color(getLastColor(nametag.prefix(), plugin))
|
||||
.prefix(nametag.getPrefixComponent(plugin, tabPlayer))
|
||||
.suffix(nametag.getSuffixComponent(plugin, tabPlayer))
|
||||
.entities(Arrays.asList(teamMembers));
|
||||
}
|
||||
|
||||
private static boolean isNametagPresent(@NotNull TabPlayer.Nametag nametag, @NotNull Velocitab plugin) {
|
||||
private static boolean isNametagPresent(@NotNull Nametag nametag, @NotNull Velocitab plugin) {
|
||||
if (!plugin.getSettings().isRemoveNametags()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !nametag.getPrefix().isEmpty() || !nametag.getSuffix().isEmpty();
|
||||
return !nametag.prefix().isEmpty() || !nametag.suffix().isEmpty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected static UpdateTeamsPacket changeNametag(@NotNull Velocitab plugin, @NotNull String teamName,
|
||||
@NotNull TabPlayer.Nametag nametag) {
|
||||
protected static UpdateTeamsPacket changeNametag(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer,
|
||||
@NotNull String teamName,
|
||||
@NotNull Nametag nametag) {
|
||||
return new UpdateTeamsPacket(plugin)
|
||||
.teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName)
|
||||
.mode(UpdateMode.UPDATE_INFO)
|
||||
@ -98,9 +101,9 @@ public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
|
||||
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
|
||||
.collisionRule(CollisionRule.ALWAYS)
|
||||
.color(getLastColor(nametag.getPrefix()))
|
||||
.prefix(nametag.getPrefixComponent(plugin))
|
||||
.suffix(nametag.getSuffixComponent(plugin));
|
||||
.color(getLastColor(nametag.prefix(), plugin))
|
||||
.prefix(nametag.getPrefixComponent(plugin, tabPlayer))
|
||||
.suffix(nametag.getSuffixComponent(plugin, tabPlayer));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -128,7 +131,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
.mode(UpdateMode.REMOVE_TEAM);
|
||||
}
|
||||
|
||||
public static int getLastColor(@Nullable String text) {
|
||||
public static int getLastColor(@Nullable String text, @NotNull Velocitab plugin) {
|
||||
if (text == null) {
|
||||
return 15;
|
||||
}
|
||||
@ -137,8 +140,8 @@ public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
text = text + "z";
|
||||
|
||||
//serialize & deserialize to downsample rgb to legacy
|
||||
Component legacyComponent = LegacyComponentSerializer.legacyAmpersand().deserialize(text);
|
||||
text = LegacyComponentSerializer.legacyAmpersand().serialize(legacyComponent);
|
||||
Component component = plugin.getFormatter().emptyFormat(text);
|
||||
text = LegacyComponentSerializer.legacyAmpersand().serialize(component);
|
||||
|
||||
int lastFormatIndex = text.lastIndexOf("&");
|
||||
if (lastFormatIndex == -1 || lastFormatIndex == text.length() - 1) {
|
||||
|
@ -73,6 +73,4 @@ public class Role implements Comparable<Role> {
|
||||
return Integer.toString(weight);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -20,12 +20,13 @@
|
||||
package net.william278.velocitab.player;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.config.Group;
|
||||
import net.william278.velocitab.config.Placeholder;
|
||||
import net.william278.velocitab.tab.Nametag;
|
||||
import net.william278.velocitab.tab.PlayerTabList;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -33,38 +34,33 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
|
||||
private final Player player;
|
||||
@Setter
|
||||
private Role role;
|
||||
@Getter
|
||||
private int headerIndex = 0;
|
||||
@Getter
|
||||
private int footerIndex = 0;
|
||||
@Getter
|
||||
private Component lastDisplayname;
|
||||
private String teamName;
|
||||
@Nullable
|
||||
@Setter
|
||||
private String customName;
|
||||
@Nullable
|
||||
@Setter
|
||||
private String lastServer;
|
||||
@NotNull
|
||||
@Setter
|
||||
private Group group;
|
||||
@Setter
|
||||
private boolean loaded;
|
||||
|
||||
public TabPlayer(@NotNull Player player, @NotNull Role role) {
|
||||
public TabPlayer(@NotNull Player player, @NotNull Role role, @NotNull Group group) {
|
||||
this.player = player;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Role getRole() {
|
||||
return role;
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -85,17 +81,6 @@ public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
.orElse(ObjectUtils.firstNonNull(lastServer, "unknown"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TAB server group this player is connected to
|
||||
*
|
||||
* @param plugin instance of the {@link Velocitab} plugin
|
||||
* @return the name of the server group the player is on
|
||||
*/
|
||||
@NotNull
|
||||
public String getServerGroup(@NotNull Velocitab plugin) {
|
||||
return plugin.getSettings().getServerGroup(this.getServerName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ordinal position of the TAB server group this player is connected to
|
||||
*
|
||||
@ -103,7 +88,7 @@ public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
* @return The ordinal position of the server group
|
||||
*/
|
||||
public int getServerGroupPosition(@NotNull Velocitab plugin) {
|
||||
return plugin.getSettings().getServerGroupPosition(getServerGroup(plugin));
|
||||
return plugin.getTabGroups().getPosition(group);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,17 +105,14 @@ public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
|
||||
@NotNull
|
||||
public CompletableFuture<Component> getDisplayName(@NotNull Velocitab plugin) {
|
||||
final String serverGroup = plugin.getSettings().getServerGroup(getServerName());
|
||||
return Placeholder.replace(plugin.getSettings().getFormat(serverGroup), plugin, this)
|
||||
return Placeholder.replace(group.format(), plugin, this)
|
||||
.thenApply(formatted -> plugin.getFormatter().format(formatted, this, plugin))
|
||||
.thenApply(c -> this.lastDisplayname = c);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CompletableFuture<Nametag> getNametag(@NotNull Velocitab plugin) {
|
||||
final String serverGroup = plugin.getSettings().getServerGroup(getServerName());
|
||||
return Placeholder.replace(plugin.getSettings().getNametag(serverGroup), plugin, this)
|
||||
.thenApply(n -> new Nametag(n, player));
|
||||
return Placeholder.replace(group.nametag(), plugin, this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -143,22 +125,26 @@ public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
return Optional.ofNullable(teamName);
|
||||
}
|
||||
|
||||
|
||||
public void sendHeaderAndFooter(@NotNull PlayerTabList tabList) {
|
||||
tabList.getHeader(this).thenAccept(header -> tabList.getFooter(this)
|
||||
public CompletableFuture<Void> sendHeaderAndFooter(@NotNull PlayerTabList tabList) {
|
||||
return tabList.getHeader(this).thenCompose(header -> tabList.getFooter(this)
|
||||
.thenAccept(footer -> player.sendPlayerListHeaderAndFooter(header, footer)));
|
||||
}
|
||||
|
||||
public void incrementHeaderIndex(@NotNull Velocitab plugin) {
|
||||
public void incrementIndexes() {
|
||||
incrementHeaderIndex();
|
||||
incrementFooterIndex();
|
||||
}
|
||||
|
||||
public void incrementHeaderIndex() {
|
||||
headerIndex++;
|
||||
if (headerIndex >= plugin.getSettings().getHeaderListSize(getServerGroup(plugin))) {
|
||||
if (headerIndex >= group.headers().size()) {
|
||||
headerIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementFooterIndex(@NotNull Velocitab plugin) {
|
||||
public void incrementFooterIndex() {
|
||||
footerIndex++;
|
||||
if (footerIndex >= plugin.getSettings().getFooterListSize(getServerGroup(plugin))) {
|
||||
if (footerIndex >= group.footers().size()) {
|
||||
footerIndex = 0;
|
||||
}
|
||||
}
|
||||
@ -172,15 +158,6 @@ public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
return Optional.ofNullable(customName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom name of the TabPlayer.
|
||||
*
|
||||
* @param customName The custom name to set
|
||||
*/
|
||||
public void setCustomName(@Nullable String customName) {
|
||||
this.customName = customName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull TabPlayer o) {
|
||||
final int roleDifference = role.compareTo(o.role);
|
||||
@ -195,40 +172,18 @@ public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
return obj instanceof TabPlayer other && player.getUniqueId().equals(other.player.getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a nametag to be displayed above a player, with prefix and suffix
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class Nametag {
|
||||
@NotNull
|
||||
private final String prefix;
|
||||
@NotNull
|
||||
private final String suffix;
|
||||
|
||||
private Nametag(@NotNull String tag, @NotNull Player player) {
|
||||
final String[] split = tag.split(Pattern.quote(player.getUsername()), 2);
|
||||
this.prefix = split[0];
|
||||
this.suffix = split.length > 1 ? split[1] : "";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Component getPrefixComponent(@NotNull Velocitab plugin) {
|
||||
return plugin.getFormatter().format(prefix, TabPlayer.this, plugin);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Component getSuffixComponent(@NotNull Velocitab plugin) {
|
||||
return plugin.getFormatter().format(suffix, TabPlayer.this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Nametag other)) {
|
||||
return false;
|
||||
}
|
||||
return (prefix.equals(other.prefix)) && (suffix.equals(other.suffix));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TabPlayer{" +
|
||||
"player=" + player +
|
||||
", role=" + role +
|
||||
", headerIndex=" + headerIndex +
|
||||
", footerIndex=" + footerIndex +
|
||||
", lastDisplayname=" + lastDisplayname +
|
||||
", teamName='" + teamName + '\'' +
|
||||
", lastServer='" + lastServer + '\'' +
|
||||
", group=" + group.name() +
|
||||
", loaded=" + loaded +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.providers;
|
||||
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.hook.Hook;
|
||||
import net.william278.velocitab.hook.LuckPermsHook;
|
||||
import net.william278.velocitab.hook.MiniPlaceholdersHook;
|
||||
import net.william278.velocitab.hook.PAPIProxyBridgeHook;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface HookProvider {
|
||||
|
||||
/**
|
||||
* Retrieves the list of hooks associated with the HookProvider.
|
||||
*
|
||||
* @return The list of hooks associated with the HookProvider.
|
||||
*/
|
||||
List<Hook> getHooks();
|
||||
|
||||
/**
|
||||
* Sets the list of hooks associated with the HookProvider.
|
||||
*
|
||||
* @param hooks The list of hooks to set.
|
||||
*/
|
||||
void setHooks(List<Hook> hooks);
|
||||
|
||||
/**
|
||||
* Retrieves the instance of the Velocitab plugin.
|
||||
*
|
||||
* @return The instance of the Velocitab plugin.
|
||||
*/
|
||||
Velocitab getPlugin();
|
||||
|
||||
/**
|
||||
* Loads the hooks associated with the HookProvider.
|
||||
*/
|
||||
default void loadHooks() {
|
||||
List<Hook> hooks = new ArrayList<>();
|
||||
Hook.AVAILABLE.forEach(availableHook -> availableHook.apply(getPlugin()).ifPresent(hooks::add));
|
||||
setHooks(hooks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a hook of the specified type from the list of hooks associated with the HookProvider.
|
||||
*
|
||||
* @param hookType The class object representing the type of the hook to retrieve.
|
||||
* @param <H> The type of the hook to retrieve.
|
||||
* @return An Optional containing the hook of the specified type, or an empty Optional if the hook is not found.
|
||||
*/
|
||||
private <H extends Hook> Optional<H> getHook(@NotNull Class<H> hookType) {
|
||||
return getHooks().stream()
|
||||
.filter(hook -> hook.getClass().equals(hookType))
|
||||
.map(hookType::cast)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the LuckPermsHook from the list of hooks associated with the HookProvider.
|
||||
*
|
||||
* @return An Optional containing the LuckPermsHook, or an empty Optional if it is not found.
|
||||
*/
|
||||
default Optional<LuckPermsHook> getLuckPermsHook() {
|
||||
return getHook(LuckPermsHook.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the PAPIProxyBridgeHook from the list of hooks associated with the HookProvider.
|
||||
*
|
||||
* @return An Optional containing the PAPIProxyBridgeHook, or an empty Optional if it is not found.
|
||||
*/
|
||||
default Optional<PAPIProxyBridgeHook> getPAPIProxyBridgeHook() {
|
||||
return getHook(PAPIProxyBridgeHook.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the MiniPlaceholdersHook from the list of hooks associated with the HookProvider.
|
||||
*
|
||||
* @return An Optional containing the MiniPlaceholdersHook, or an empty Optional if it is not found.
|
||||
*/
|
||||
default Optional<MiniPlaceholdersHook> getMiniPlaceholdersHook() {
|
||||
return getHook(MiniPlaceholdersHook.class);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.providers;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
public interface LoggerProvider {
|
||||
|
||||
/**
|
||||
* Retrieves the logger for the corresponding class.
|
||||
*
|
||||
* @return the logger for the class
|
||||
*/
|
||||
Logger getLogger();
|
||||
|
||||
/**
|
||||
* Logs a message with the specified log level.
|
||||
*
|
||||
* @param level the log level
|
||||
* @param message the log message
|
||||
* @param exceptions the exceptions associated with the log message (optional)
|
||||
*/
|
||||
default void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... exceptions) {
|
||||
switch (level) {
|
||||
case ERROR -> {
|
||||
if (exceptions.length > 0) {
|
||||
getLogger().error(message, exceptions[0]);
|
||||
} else {
|
||||
getLogger().error(message);
|
||||
}
|
||||
}
|
||||
case WARN -> {
|
||||
if (exceptions.length > 0) {
|
||||
getLogger().warn(message, exceptions[0]);
|
||||
} else {
|
||||
getLogger().warn(message);
|
||||
}
|
||||
}
|
||||
case INFO -> getLogger().info(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message with the specified log level.
|
||||
*/
|
||||
default void log(@NotNull String message) {
|
||||
this.log(Level.INFO, message);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.providers;
|
||||
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bstats.velocity.Metrics;
|
||||
|
||||
public interface MetricProvider {
|
||||
|
||||
int METRICS_ID = 18247;
|
||||
|
||||
/**
|
||||
* Retrieves the Metrics Factory used by the MetricProvider.
|
||||
*
|
||||
* @return The Metrics Factory used by the MetricProvider.
|
||||
*/
|
||||
Metrics.Factory getMetricsFactory();
|
||||
|
||||
/**
|
||||
* Retrieves the Velocitab plugin instance.
|
||||
* @return
|
||||
*/
|
||||
Velocitab getPlugin();
|
||||
|
||||
/**
|
||||
* Registers metrics for the Velocitab plugin using the Metrics library.
|
||||
* This method adds custom charts to the metrics object, which include information such as:
|
||||
* - Whether player sorting is enabled or disabled
|
||||
* - The type of formatter being used
|
||||
* - Whether LuckPerms hook is present
|
||||
* - Whether PAPIProxyBridge hook is present
|
||||
* - Whether MiniPlaceholders hook is present
|
||||
*/
|
||||
default void registerMetrics() {
|
||||
final Metrics metrics = getMetricsFactory().make(this, METRICS_ID);
|
||||
metrics.addCustomChart(new SimplePie("sort_players", () -> getPlugin().getSettings().isSortPlayers() ? "Enabled" : "Disabled"));
|
||||
metrics.addCustomChart(new SimplePie("formatter_type", () -> getPlugin().getFormatter().getName()));
|
||||
metrics.addCustomChart(new SimplePie("using_luckperms", () -> getPlugin().getLuckPermsHook().isPresent() ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie("using_papiproxybridge", () -> getPlugin().getPAPIProxyBridgeHook().isPresent() ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie("using_miniplaceholders", () -> getPlugin().getMiniPlaceholdersHook().isPresent() ? "Yes" : "No"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.providers;
|
||||
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.packet.ScoreboardManager;
|
||||
import net.william278.velocitab.sorting.SortingManager;
|
||||
import net.william278.velocitab.tab.PlayerTabList;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface ScoreboardProvider {
|
||||
|
||||
/**
|
||||
* Retrieves the Velocitab plugin instance.
|
||||
*
|
||||
* @return The Velocitab plugin instance.
|
||||
*/
|
||||
Velocitab getPlugin();
|
||||
|
||||
/**
|
||||
* Retrieves the optional scoreboard manager.
|
||||
*
|
||||
* @return An {@code Optional} object that may contain a {@code ScoreboardManager} instance.
|
||||
*/
|
||||
Optional<ScoreboardManager> getScoreboardManager();
|
||||
|
||||
/**
|
||||
* Sets the scoreboard manager.
|
||||
*
|
||||
* @param scoreboardManager The scoreboard manager to be set.
|
||||
*/
|
||||
void setScoreboardManager(ScoreboardManager scoreboardManager);
|
||||
|
||||
/**
|
||||
* Retrieves the tab list for the player.
|
||||
*
|
||||
* @return The PlayerTabList object representing the tab list for the player.
|
||||
*/
|
||||
PlayerTabList getTabList();
|
||||
|
||||
/**
|
||||
* Sets the tab list for the player.
|
||||
*
|
||||
* @param tabList The PlayerTabList object representing the tab list to be set for the player.
|
||||
*/
|
||||
void setTabList(PlayerTabList tabList);
|
||||
|
||||
/**
|
||||
* Returns the SortingManager instance.
|
||||
*
|
||||
* @return The SortingManager instance.
|
||||
*/
|
||||
SortingManager getSortingManager();
|
||||
|
||||
/**
|
||||
* Sets the sorting manager for the ScoreboardProvider.
|
||||
*
|
||||
* @param sortingManager The sorting manager to be set.
|
||||
*/
|
||||
void setSortingManager(SortingManager sortingManager);
|
||||
|
||||
/**
|
||||
* Prepares the scoreboard by initializing the necessary components.
|
||||
* This method is responsible for setting up the scoreboard manager, player tab list,
|
||||
* scheduler tasks, and sorting manager.
|
||||
*
|
||||
*/
|
||||
default void prepareScoreboard() {
|
||||
if (getPlugin().getSettings().isSendScoreboardPackets()) {
|
||||
ScoreboardManager scoreboardManager = new ScoreboardManager(getPlugin());
|
||||
setScoreboardManager(scoreboardManager);
|
||||
scoreboardManager.registerPacket();
|
||||
}
|
||||
|
||||
final PlayerTabList tabList = new PlayerTabList(getPlugin());
|
||||
setTabList(tabList);
|
||||
getPlugin().getServer().getEventManager().register(this, tabList);
|
||||
|
||||
getPlugin().getServer().getScheduler().buildTask(this, tabList::load).delay(1, TimeUnit.SECONDS).schedule();
|
||||
|
||||
final SortingManager sortingManager = new SortingManager(getPlugin());
|
||||
setSortingManager(sortingManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the ScoreboardManager and closes the tab list for the player.
|
||||
*/
|
||||
default void disableScoreboardManager() {
|
||||
if (getScoreboardManager().isPresent() && getPlugin().getSettings().isSendScoreboardPackets()) {
|
||||
getScoreboardManager().get().close();
|
||||
getScoreboardManager().get().unregisterPacket();
|
||||
}
|
||||
|
||||
getTabList().close();
|
||||
}
|
||||
|
||||
}
|
@ -22,6 +22,7 @@ package net.william278.velocitab.sorting;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.config.Placeholder;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -35,22 +36,24 @@ public class SortingManager {
|
||||
private final Velocitab plugin;
|
||||
private static final String DELIMITER = ":::";
|
||||
|
||||
public SortingManager(Velocitab plugin) {
|
||||
public SortingManager(@NotNull Velocitab plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public CompletableFuture<String> getTeamName(TabPlayer player) {
|
||||
@NotNull
|
||||
public CompletableFuture<String> getTeamName(@NotNull TabPlayer player) {
|
||||
if (!plugin.getSettings().isSortPlayers()) {
|
||||
return CompletableFuture.completedFuture("");
|
||||
}
|
||||
|
||||
return Placeholder.replace(String.join(DELIMITER, plugin.getSettings().getSortingElements()), plugin, player)
|
||||
return Placeholder.replace(String.join(DELIMITER, player.getGroup().sortingPlaceholders()), plugin, player)
|
||||
.thenApply(s -> Arrays.asList(s.split(DELIMITER)))
|
||||
.thenApply(v -> v.stream().map(this::adaptValue).collect(Collectors.toList()))
|
||||
.thenApply(v -> handleList(player, v));
|
||||
}
|
||||
|
||||
private String handleList(TabPlayer player, List<String> values) {
|
||||
@NotNull
|
||||
private String handleList(@NotNull TabPlayer player, @NotNull List<String> values) {
|
||||
String result = String.join("", values);
|
||||
|
||||
if (result.length() > 12) {
|
||||
@ -63,7 +66,8 @@ public class SortingManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
private String adaptValue(String value) {
|
||||
@NotNull
|
||||
private String adaptValue(@NotNull String value) {
|
||||
if (value.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
@ -81,6 +85,7 @@ public class SortingManager {
|
||||
return value;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String compressNumber(double number) {
|
||||
int wholePart = (int) number;
|
||||
final char decimalChar = (char) ((number - wholePart) * Character.MAX_VALUE);
|
||||
|
50
src/main/java/net/william278/velocitab/tab/Nametag.java
Normal file
50
src/main/java/net/william278/velocitab/tab/Nametag.java
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.tab;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a nametag to be displayed above a player, with prefix and suffix
|
||||
*/
|
||||
public record Nametag(@NotNull String prefix, @NotNull String suffix) {
|
||||
|
||||
@NotNull
|
||||
public Component getPrefixComponent(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer) {
|
||||
return plugin.getFormatter().format(prefix, tabPlayer, plugin);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Component getSuffixComponent(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer) {
|
||||
return plugin.getFormatter().format(suffix, tabPlayer, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Nametag other)) {
|
||||
return false;
|
||||
}
|
||||
return (prefix.equals(other.prefix)) && (suffix.equals(other.suffix));
|
||||
}
|
||||
|
||||
}
|
@ -19,12 +19,8 @@
|
||||
|
||||
package net.william278.velocitab.tab;
|
||||
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.player.TabList;
|
||||
@ -32,41 +28,46 @@ import com.velocitypowered.api.proxy.player.TabListEntry;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.api.PlayerAddedToTabEvent;
|
||||
import net.william278.velocitab.config.Group;
|
||||
import net.william278.velocitab.config.Placeholder;
|
||||
import net.william278.velocitab.player.Role;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The main class for tracking the server TAB list
|
||||
*/
|
||||
public class PlayerTabList {
|
||||
private final Velocitab plugin;
|
||||
private final ConcurrentHashMap<UUID, TabPlayer> players;
|
||||
private final ConcurrentLinkedQueue<String> fallbackServers;
|
||||
private final List<UUID> justKicked;
|
||||
private ScheduledTask updateTask;
|
||||
@Getter
|
||||
private final VanishTabList vanishTabList;
|
||||
@Getter(value = AccessLevel.PUBLIC)
|
||||
private final Map<UUID, TabPlayer> players;
|
||||
private final Map<Group, ScheduledTask> placeholderTasks;
|
||||
private final Map<Group, ScheduledTask> headerFooterTasks;
|
||||
|
||||
public PlayerTabList(@NotNull Velocitab plugin) {
|
||||
this.plugin = plugin;
|
||||
this.players = new ConcurrentHashMap<>();
|
||||
this.fallbackServers = new ConcurrentLinkedQueue<>();
|
||||
this.justKicked = new CopyOnWriteArrayList<>();
|
||||
this.vanishTabList = new VanishTabList(plugin, this);
|
||||
this.players = Maps.newConcurrentMap();
|
||||
this.placeholderTasks = Maps.newConcurrentMap();
|
||||
this.headerFooterTasks = Maps.newConcurrentMap();
|
||||
this.reloadUpdate();
|
||||
this.registerListener();
|
||||
}
|
||||
|
||||
// If the update time is set to 0 do not schedule the updater
|
||||
if (plugin.getSettings().getUpdateRate() > 0) {
|
||||
this.updatePeriodically(plugin.getSettings().getUpdateRate());
|
||||
}
|
||||
private void registerListener() {
|
||||
plugin.getServer().getEventManager().register(plugin, new TabListListener(plugin, this));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,6 +80,15 @@ public class PlayerTabList {
|
||||
return Optional.ofNullable(players.get(player.getUniqueId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a TabPlayer object corresponding to the given UUID.
|
||||
*
|
||||
* @param uuid The UUID of the player for which to retrieve the corresponding TabPlayer.
|
||||
* @return An Optional object containing the TabPlayer if found, or an empty Optional if not found.
|
||||
*/
|
||||
public Optional<TabPlayer> getTabPlayer(@NotNull UUID uuid) {
|
||||
return Optional.ofNullable(players.get(uuid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the tab list for all players connected to the server.
|
||||
@ -89,12 +99,15 @@ public class PlayerTabList {
|
||||
final Optional<ServerConnection> server = p.getCurrentServer();
|
||||
if (server.isEmpty()) return;
|
||||
|
||||
final List<RegisteredServer> serversInGroup = new ArrayList<>(getGroupServers(server.get().getServerInfo().getName()));
|
||||
if (serversInGroup.isEmpty()) return;
|
||||
final String serverName = server.get().getServerInfo().getName();
|
||||
final Group group = getGroup(serverName);
|
||||
final boolean isDefault = !group.servers().contains(serverName);
|
||||
|
||||
serversInGroup.remove(server.get().getServer());
|
||||
if (isDefault && !plugin.getSettings().isFallbackEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
joinPlayer(p, serversInGroup.stream().map(s -> s.getServerInfo().getName()).toList());
|
||||
joinPlayer(p, group);
|
||||
});
|
||||
}
|
||||
|
||||
@ -103,118 +116,113 @@ public class PlayerTabList {
|
||||
* Removes the player's entry from the tab list of all other players on the same group servers.
|
||||
*/
|
||||
public void close() {
|
||||
placeholderTasks.values().forEach(ScheduledTask::cancel);
|
||||
placeholderTasks.clear();
|
||||
headerFooterTasks.values().forEach(ScheduledTask::cancel);
|
||||
headerFooterTasks.clear();
|
||||
plugin.getServer().getAllPlayers().forEach(p -> {
|
||||
final Optional<ServerConnection> server = p.getCurrentServer();
|
||||
if (server.isEmpty()) return;
|
||||
|
||||
final List<RegisteredServer> serversInGroup = new ArrayList<>(getGroupServers(server.get().getServerInfo().getName()));
|
||||
if (serversInGroup.isEmpty()) return;
|
||||
final TabPlayer tabPlayer = players.get(p.getUniqueId());
|
||||
if (tabPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<RegisteredServer> serversInGroup = new ArrayList<>(tabPlayer.getGroup().registeredServers(plugin));
|
||||
if (serversInGroup.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
serversInGroup.remove(server.get().getServer());
|
||||
|
||||
serversInGroup.forEach(s -> s.getPlayersConnected().forEach(t -> t.getTabList().removeEntry(p.getUniqueId())));
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onKick(KickedFromServerEvent event) {
|
||||
event.getPlayer().getTabList().clearAll();
|
||||
event.getPlayer().getTabList().clearHeaderAndFooter();
|
||||
justKicked.add(event.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@Subscribe
|
||||
public void onPlayerJoin(@NotNull ServerPostConnectEvent event) {
|
||||
final Player joined = event.getPlayer();
|
||||
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(joined));
|
||||
|
||||
final RegisteredServer previousServer = event.getPreviousServer();
|
||||
|
||||
// Get the servers in the group from the joined server name
|
||||
// If the server is not in a group, use fallback
|
||||
final Optional<List<String>> serversInGroup = getGroupNames(joined.getCurrentServer()
|
||||
.map(ServerConnection::getServerInfo)
|
||||
.map(ServerInfo::getName)
|
||||
.orElse("?"));
|
||||
|
||||
// If the server is not in a group, use fallback.
|
||||
// If fallback is disabled, permit the player to switch excluded servers without a header or footer override
|
||||
if (serversInGroup.isEmpty() &&
|
||||
(previousServer != null && !this.fallbackServers.contains(previousServer.getServerInfo().getName()))) {
|
||||
event.getPlayer().sendPlayerListHeaderAndFooter(Component.empty(), Component.empty());
|
||||
players.remove(event.getPlayer().getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
joinPlayer(joined, serversInGroup.orElseGet(ArrayList::new));
|
||||
}
|
||||
|
||||
private void joinPlayer(@NotNull Player joined, @NotNull List<String> serversInGroup) {
|
||||
protected void joinPlayer(@NotNull Player joined, @NotNull Group group) {
|
||||
// Add the player to the tracking list if they are not already listed
|
||||
final TabPlayer tabPlayer = getTabPlayer(joined).orElseGet(() -> createTabPlayer(joined));
|
||||
final TabPlayer tabPlayer = getTabPlayer(joined).orElseGet(() -> createTabPlayer(joined, group));
|
||||
tabPlayer.setGroup(group);
|
||||
players.putIfAbsent(joined.getUniqueId(), tabPlayer);
|
||||
|
||||
int delay = 500;
|
||||
|
||||
if (justKicked.contains(joined.getUniqueId())) {
|
||||
delay = 1000;
|
||||
justKicked.remove(joined.getUniqueId());
|
||||
}
|
||||
|
||||
//store last server so it's possible to have the last server on disconnect
|
||||
//store last server, so it's possible to have the last server on disconnect
|
||||
tabPlayer.setLastServer(joined.getCurrentServer().map(ServerConnection::getServerInfo).map(ServerInfo::getName).orElse(""));
|
||||
|
||||
final boolean isVanished = plugin.getVanishManager().isVanished(joined.getUsername());
|
||||
// Update lists
|
||||
plugin.getServer().getScheduler()
|
||||
.buildTask(plugin, () -> {
|
||||
final TabList tabList = joined.getTabList();
|
||||
for (final TabPlayer player : players.values()) {
|
||||
// Skip players on other servers if the setting is enabled
|
||||
if (plugin.getSettings().isOnlyListPlayersInSameGroup()
|
||||
&& !serversInGroup.contains(player.getServerName())) {
|
||||
continue;
|
||||
}
|
||||
// check if current player can see the joined player
|
||||
if (!isVanished || plugin.getVanishManager().canSee(player.getPlayer().getUsername(), joined.getUsername())) {
|
||||
addPlayerToTabList(player, tabPlayer);
|
||||
} else {
|
||||
player.getPlayer().getTabList().removeEntry(joined.getUniqueId());
|
||||
}
|
||||
// check if joined player can see current player
|
||||
if ((plugin.getVanishManager().isVanished(player.getPlayer().getUsername()) &&
|
||||
!plugin.getVanishManager().canSee(joined.getUsername(), player.getPlayer().getUsername())) && player.getPlayer() != joined) {
|
||||
tabList.removeEntry(player.getPlayer().getUniqueId());
|
||||
} else {
|
||||
tabList.getEntry(player.getPlayer().getUniqueId()).ifPresentOrElse(
|
||||
entry -> player.getDisplayName(plugin).thenAccept(entry::setDisplayName)
|
||||
.exceptionally(throwable -> {
|
||||
plugin.log(Level.ERROR, String.format("Failed to set display name for %s (UUID: %s)",
|
||||
player.getPlayer().getUsername(), player.getPlayer().getUniqueId()), throwable);
|
||||
return null;
|
||||
}),
|
||||
() -> createEntry(player, tabList).thenAccept(tabList::addEntry)
|
||||
);
|
||||
}
|
||||
final boolean isDefault = group.isDefault();
|
||||
final boolean isFallback = isDefault && plugin.getSettings().isFallbackEnabled();
|
||||
|
||||
player.sendHeaderAndFooter(this);
|
||||
}
|
||||
tabPlayer.getDisplayName(plugin).thenAccept(d -> {
|
||||
|
||||
plugin.getScoreboardManager().ifPresent(s -> {
|
||||
s.resendAllTeams(joined);
|
||||
tabPlayer.getTeamName(plugin).thenAccept(t -> s.updateRole(joined, t, false));
|
||||
joined.getTabList().getEntry(joined.getUniqueId())
|
||||
.ifPresentOrElse(e -> e.setDisplayName(d),
|
||||
() -> joined.getTabList().addEntry(createEntry(tabPlayer, joined.getTabList(), d)));
|
||||
|
||||
tabPlayer.sendHeaderAndFooter(this)
|
||||
.thenAccept(v -> tabPlayer.setLoaded(true))
|
||||
.exceptionally(throwable -> {
|
||||
plugin.log(Level.ERROR, String.format("Failed to send header and footer for %s (UUID: %s)",
|
||||
joined.getUsername(), joined.getUniqueId()), throwable);
|
||||
return null;
|
||||
});
|
||||
|
||||
// Fire event without listening for result
|
||||
plugin.getServer().getEventManager().fireAndForget(new PlayerAddedToTabEvent(tabPlayer, tabPlayer.getServerGroup(plugin), serversInGroup));
|
||||
})
|
||||
.delay(delay, TimeUnit.MILLISECONDS)
|
||||
.schedule();
|
||||
// Update lists
|
||||
plugin.getServer().getScheduler()
|
||||
.buildTask(plugin, () -> {
|
||||
final TabList tabList = joined.getTabList();
|
||||
for (final TabPlayer player : players.values()) {
|
||||
// Skip players on other servers if the setting is enabled
|
||||
if (plugin.getSettings().isOnlyListPlayersInSameGroup()
|
||||
&& !isFallback &&
|
||||
!group.servers().contains(player.getServerName())
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// check if current player can see the joined player
|
||||
if (!isVanished || plugin.getVanishManager().canSee(player.getPlayer().getUsername(), joined.getUsername())) {
|
||||
addPlayerToTabList(player, tabPlayer, d);
|
||||
} else {
|
||||
player.getPlayer().getTabList().removeEntry(joined.getUniqueId());
|
||||
}
|
||||
// check if joined player can see current player
|
||||
if ((plugin.getVanishManager().isVanished(player.getPlayer().getUsername()) &&
|
||||
!plugin.getVanishManager().canSee(joined.getUsername(), player.getPlayer().getUsername())) && player.getPlayer() != joined) {
|
||||
tabList.removeEntry(player.getPlayer().getUniqueId());
|
||||
} else {
|
||||
tabList.getEntry(player.getPlayer().getUniqueId()).ifPresentOrElse(
|
||||
entry -> player.getDisplayName(plugin).thenAccept(entry::setDisplayName)
|
||||
.exceptionally(throwable -> {
|
||||
plugin.log(Level.ERROR, String.format("Failed to set display name for %s (UUID: %s)",
|
||||
player.getPlayer().getUsername(), player.getPlayer().getUniqueId()), throwable);
|
||||
return null;
|
||||
}),
|
||||
() -> createEntry(player, tabList).thenAccept(tabList::addEntry)
|
||||
);
|
||||
}
|
||||
|
||||
player.sendHeaderAndFooter(this);
|
||||
}
|
||||
|
||||
plugin.getScoreboardManager().ifPresent(s -> {
|
||||
s.resendAllTeams(tabPlayer);
|
||||
tabPlayer.getTeamName(plugin).thenAccept(t -> s.updateRole(tabPlayer, t, false));
|
||||
});
|
||||
|
||||
// Fire event without listening for result
|
||||
plugin.getServer().getEventManager().fireAndForget(new PlayerAddedToTabEvent(tabPlayer, group));
|
||||
})
|
||||
.delay(300, TimeUnit.MILLISECONDS)
|
||||
.schedule();
|
||||
}).exceptionally(throwable -> {
|
||||
plugin.log(Level.ERROR, String.format("Failed to set display name for %s (UUID: %s)",
|
||||
joined.getUsername(), joined.getUniqueId()), throwable);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CompletableFuture<TabListEntry> createEntry(@NotNull TabPlayer player, @NotNull TabList tabList) {
|
||||
CompletableFuture<TabListEntry> createEntry(@NotNull TabPlayer player, @NotNull TabList tabList) {
|
||||
return player.getDisplayName(plugin).thenApply(name -> TabListEntry.builder()
|
||||
.profile(player.getPlayer().getGameProfile())
|
||||
.displayName(name)
|
||||
@ -223,7 +231,7 @@ public class PlayerTabList {
|
||||
.build());
|
||||
}
|
||||
|
||||
private TabListEntry createEntry(@NotNull TabPlayer player, @NotNull TabList tabList, @NotNull Component displayName) {
|
||||
protected TabListEntry createEntry(@NotNull TabPlayer player, @NotNull TabList tabList, @NotNull Component displayName) {
|
||||
return TabListEntry.builder()
|
||||
.profile(player.getPlayer().getGameProfile())
|
||||
.displayName(displayName)
|
||||
@ -232,57 +240,34 @@ public class PlayerTabList {
|
||||
.build();
|
||||
}
|
||||
|
||||
private void addPlayerToTabList(@NotNull TabPlayer player, @NotNull TabPlayer newPlayer) {
|
||||
private void addPlayerToTabList(@NotNull TabPlayer player, @NotNull TabPlayer newPlayer, @NotNull Component displayName) {
|
||||
if (newPlayer.getPlayer().getUniqueId().equals(player.getPlayer().getUniqueId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getPacketEventManager().getVelocitabEntries().add(newPlayer.getPlayer().getUniqueId());
|
||||
|
||||
plugin.getServer().getScheduler()
|
||||
.buildTask(plugin, () -> plugin.getPacketEventManager().getVelocitabEntries().remove(newPlayer.getPlayer().getUniqueId()))
|
||||
.delay(500, TimeUnit.MILLISECONDS)
|
||||
.schedule();
|
||||
|
||||
player.getPlayer()
|
||||
.getTabList().getEntries().stream()
|
||||
.filter(e -> e.getProfile().getId().equals(newPlayer.getPlayer().getUniqueId())).findFirst()
|
||||
.ifPresentOrElse(
|
||||
entry -> newPlayer.getDisplayName(plugin).thenAccept(entry::setDisplayName),
|
||||
() -> createEntry(newPlayer, player.getPlayer().getTabList())
|
||||
.thenAccept(entry -> player.getPlayer().getTabList().addEntry(entry))
|
||||
entry -> entry.setDisplayName(displayName),
|
||||
() -> player.getPlayer().getTabList()
|
||||
.addEntry(createEntry(newPlayer, player.getPlayer().getTabList(), displayName))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
public void onPlayerQuit(@NotNull DisconnectEvent event) {
|
||||
if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the player from the tracking list, Print warning if player was not removed
|
||||
final UUID uuid = event.getPlayer().getUniqueId();
|
||||
final TabPlayer tabPlayer = players.get(uuid);
|
||||
if (tabPlayer == null) {
|
||||
plugin.log(String.format("Failed to remove disconnecting player %s (UUID: %s)",
|
||||
event.getPlayer().getUsername(), uuid));
|
||||
}
|
||||
|
||||
// Remove the player from the tab list of all other players
|
||||
plugin.getServer().getAllPlayers().forEach(player -> player.getTabList().removeEntry(uuid));
|
||||
|
||||
// Update the tab list of all players
|
||||
plugin.getServer().getScheduler()
|
||||
.buildTask(plugin, () -> players.values().forEach(player -> {
|
||||
player.getPlayer().getTabList().removeEntry(uuid);
|
||||
player.sendHeaderAndFooter(this);
|
||||
}))
|
||||
.delay(500, TimeUnit.MILLISECONDS)
|
||||
.schedule();
|
||||
// Delete player team
|
||||
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(event.getPlayer()));
|
||||
//remove player from tab list cache
|
||||
players.remove(uuid);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TabPlayer createTabPlayer(@NotNull Player player) {
|
||||
public TabPlayer createTabPlayer(@NotNull Player player, @NotNull Group group) {
|
||||
return new TabPlayer(player,
|
||||
plugin.getLuckPermsHook().map(hook -> hook.getPlayerRole(player)).orElse(Role.DEFAULT_ROLE)
|
||||
plugin.getLuckPermsHook().map(hook -> hook.getPlayerRole(player)).orElse(Role.DEFAULT_ROLE),
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
@ -298,7 +283,7 @@ public class PlayerTabList {
|
||||
return;
|
||||
}
|
||||
plugin.getScoreboardManager().ifPresent(manager -> manager.updateRole(
|
||||
tabPlayer.getPlayer(), teamName, force
|
||||
tabPlayer, teamName, force
|
||||
));
|
||||
});
|
||||
}
|
||||
@ -331,8 +316,7 @@ public class PlayerTabList {
|
||||
|
||||
// Get the component for the TAB list header
|
||||
public CompletableFuture<Component> getHeader(@NotNull TabPlayer player) {
|
||||
final String header = plugin.getSettings().getHeader(player.getServerGroup(plugin), player.getHeaderIndex());
|
||||
player.incrementHeaderIndex(plugin);
|
||||
final String header = player.getGroup().getHeader(player.getHeaderIndex());
|
||||
|
||||
return Placeholder.replace(header, plugin, player)
|
||||
.thenApply(replaced -> plugin.getFormatter().format(replaced, player, plugin));
|
||||
@ -340,104 +324,116 @@ public class PlayerTabList {
|
||||
|
||||
// Get the component for the TAB list footer
|
||||
public CompletableFuture<Component> getFooter(@NotNull TabPlayer player) {
|
||||
final String footer = plugin.getSettings().getFooter(player.getServerGroup(plugin), player.getFooterIndex());
|
||||
player.incrementFooterIndex(plugin);
|
||||
final String footer = player.getGroup().getFooter(player.getFooterIndex());
|
||||
|
||||
return Placeholder.replace(footer, plugin, player)
|
||||
.thenApply(replaced -> plugin.getFormatter().format(replaced, player, plugin));
|
||||
}
|
||||
|
||||
// Update the tab list periodically
|
||||
private void updatePeriodically(int updateRate) {
|
||||
updateTask = plugin.getServer().getScheduler()
|
||||
.buildTask(plugin, () -> {
|
||||
if (players.isEmpty()) {
|
||||
return;
|
||||
private void updatePeriodically(Group group) {
|
||||
cancelTasks(group);
|
||||
|
||||
if (group.headerFooterUpdateRate() > 0) {
|
||||
final ScheduledTask headerFooterTask = plugin.getServer().getScheduler()
|
||||
.buildTask(plugin, () -> updateGroupPlayers(group, false, true))
|
||||
.delay(1, TimeUnit.SECONDS)
|
||||
.repeat(Math.max(200, group.headerFooterUpdateRate()), TimeUnit.MILLISECONDS)
|
||||
.schedule();
|
||||
headerFooterTasks.put(group, headerFooterTask);
|
||||
}
|
||||
|
||||
if (group.placeholderUpdateRate() > 0) {
|
||||
final ScheduledTask updateTask = plugin.getServer().getScheduler()
|
||||
.buildTask(plugin, () -> updateGroupPlayers(group, true, false))
|
||||
.delay(1, TimeUnit.SECONDS)
|
||||
.repeat(Math.max(200, group.placeholderUpdateRate()), TimeUnit.MILLISECONDS)
|
||||
.schedule();
|
||||
placeholderTasks.put(group, updateTask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the players in the given group.
|
||||
*
|
||||
* @param group The group whose players should be updated.
|
||||
* @param all Whether to update all player properties, or just the header and footer.
|
||||
* @param incrementIndexes Whether to increment the header and footer indexes.
|
||||
*/
|
||||
private void updateGroupPlayers(@NotNull Group group, boolean all, boolean incrementIndexes) {
|
||||
List<TabPlayer> groupPlayers = group.getTabPlayers(plugin);
|
||||
if (groupPlayers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
groupPlayers.stream()
|
||||
.filter(player -> player.getPlayer().isActive())
|
||||
.forEach(player -> {
|
||||
if (incrementIndexes) {
|
||||
player.incrementIndexes();
|
||||
}
|
||||
players.values().forEach(player -> {
|
||||
if (all) {
|
||||
this.updatePlayer(player, false);
|
||||
player.sendHeaderAndFooter(this);
|
||||
});
|
||||
updateDisplayNames();
|
||||
})
|
||||
.repeat(Math.max(200, updateRate), TimeUnit.MILLISECONDS)
|
||||
.schedule();
|
||||
}
|
||||
player.sendHeaderAndFooter(this);
|
||||
});
|
||||
if (all) {
|
||||
updateDisplayNames();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelTasks(Group group) {
|
||||
ScheduledTask task = placeholderTasks.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().equals(group))
|
||||
.map(Map.Entry::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
placeholderTasks.remove(group);
|
||||
}
|
||||
|
||||
task = headerFooterTasks.entrySet().stream()
|
||||
.filter(entry -> entry.getKey().equals(group))
|
||||
.map(Map.Entry::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
headerFooterTasks.remove(group);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the TAB list for all players when a plugin or proxy reload is performed
|
||||
*/
|
||||
public void reloadUpdate() {
|
||||
placeholderTasks.values().forEach(ScheduledTask::cancel);
|
||||
placeholderTasks.clear();
|
||||
plugin.getTabGroups().getGroups().forEach(this::updatePeriodically);
|
||||
|
||||
if (players.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel();
|
||||
}
|
||||
// If the update time is set to 0 do not schedule the updater
|
||||
if (plugin.getSettings().getUpdateRate() > 0) {
|
||||
this.updatePeriodically(plugin.getSettings().getUpdateRate());
|
||||
} else {
|
||||
players.values().forEach(player -> {
|
||||
this.updatePlayer(player, true);
|
||||
player.sendHeaderAndFooter(this);
|
||||
});
|
||||
updateDisplayNames();
|
||||
}
|
||||
|
||||
players.values().forEach(player -> {
|
||||
final Optional<ServerConnection> server = player.getPlayer().getCurrentServer();
|
||||
if (server.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final String serverName = server.get().getServerInfo().getName();
|
||||
final Group group = getGroup(serverName);
|
||||
player.setGroup(group);
|
||||
this.updatePlayer(player, true);
|
||||
player.sendHeaderAndFooter(this);
|
||||
});
|
||||
updateDisplayNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the servers in the same group as the given server, as an optional
|
||||
* <p>
|
||||
* If the server is not in a group, use the fallback group
|
||||
* If the fallback is disabled, return an empty optional
|
||||
*
|
||||
* @param serverName The server name
|
||||
* @return The servers in the same group as the given server, empty if the server is not in a group and fallback is disabled
|
||||
*/
|
||||
@NotNull
|
||||
public Optional<List<String>> getGroupNames(@NotNull String serverName) {
|
||||
return plugin.getSettings().getServerGroups().values().stream()
|
||||
.filter(servers -> servers.contains(serverName))
|
||||
.findFirst()
|
||||
.or(() -> {
|
||||
if (!plugin.getSettings().isFallbackEnabled()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (!this.fallbackServers.contains(serverName)) {
|
||||
this.fallbackServers.add(serverName);
|
||||
}
|
||||
return Optional.of(this.fallbackServers.stream().toList());
|
||||
});
|
||||
public Group getGroup(@NotNull String serverName) {
|
||||
return plugin.getTabGroups().getGroupFromServer(serverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the servers in the same group as the given server, as an optional list of {@link ServerInfo}
|
||||
* <p>
|
||||
* If the server is not in a group, use the fallback group
|
||||
* If the fallback is disabled, return an empty optional
|
||||
*
|
||||
* @param serverName The server name
|
||||
* @return The servers in the same group as the given server, empty if the server is not in a group and fallback is disabled
|
||||
*/
|
||||
@NotNull
|
||||
public List<RegisteredServer> getGroupServers(@NotNull String serverName) {
|
||||
return plugin.getServer().getAllServers().stream()
|
||||
.filter(server -> plugin.getSettings().getServerGroups().values().stream()
|
||||
.filter(servers -> servers.contains(serverName))
|
||||
.anyMatch(servers -> servers.contains(server.getServerInfo().getName())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void proxyReload(@NotNull ProxyReloadEvent event) {
|
||||
plugin.loadSettings();
|
||||
reloadUpdate();
|
||||
plugin.log("Velocitab has been reloaded!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an offline player from the list of tracked TAB players
|
||||
@ -448,81 +444,4 @@ public class PlayerTabList {
|
||||
players.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void vanishPlayer(@NotNull TabPlayer tabPlayer) {
|
||||
players.values().forEach(p -> {
|
||||
if (p.getPlayer().equals(tabPlayer.getPlayer())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!plugin.getVanishManager().canSee(p.getPlayer().getUsername(), tabPlayer.getPlayer().getUsername())) {
|
||||
p.getPlayer().getTabList().removeEntry(tabPlayer.getPlayer().getUniqueId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void unVanishPlayer(@NotNull TabPlayer tabPlayer) {
|
||||
final UUID uuid = tabPlayer.getPlayer().getUniqueId();
|
||||
|
||||
tabPlayer.getDisplayName(plugin).thenAccept(c -> players.values().forEach(p -> {
|
||||
if (p.getPlayer().equals(tabPlayer.getPlayer())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!p.getPlayer().getTabList().containsEntry(uuid)) {
|
||||
p.getPlayer().getTabList().addEntry(createEntry(tabPlayer, p.getPlayer().getTabList(), c));
|
||||
} else {
|
||||
p.getPlayer().getTabList().getEntry(uuid).ifPresent(entry -> entry.setDisplayName(c));
|
||||
}
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the visibility of players in the tab list for the given player.
|
||||
* If tabPlayer can see the player, the player will be added to the tab list.
|
||||
*
|
||||
* @param tabPlayer The TabPlayer object representing the player for whom to recalculate the tab list visibility.
|
||||
*/
|
||||
public void recalculateVanishForPlayer(@NotNull TabPlayer tabPlayer) {
|
||||
final Player player = tabPlayer.getPlayer();
|
||||
final Optional<List<String>> serversInGroupOptional = getGroupNames(player.getCurrentServer()
|
||||
.map(ServerConnection::getServerInfo)
|
||||
.map(ServerInfo::getName)
|
||||
.orElse("?"));
|
||||
final List<String> serversInGroup = serversInGroupOptional.orElseGet(ArrayList::new);
|
||||
|
||||
plugin.getServer().getAllPlayers().forEach(p -> {
|
||||
if (p.equals(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<TabPlayer> targetOptional = getTabPlayer(p);
|
||||
if (targetOptional.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TabPlayer target = targetOptional.get();
|
||||
final String serverName = target.getServerName();
|
||||
|
||||
if (plugin.getSettings().isOnlyListPlayersInSameGroup()
|
||||
&& !serversInGroup.contains(serverName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean canSee = !plugin.getVanishManager().isVanished(p.getUsername()) ||
|
||||
plugin.getVanishManager().canSee(player.getUsername(), p.getUsername());
|
||||
|
||||
if (!canSee) {
|
||||
player.getTabList().removeEntry(p.getUniqueId());
|
||||
plugin.getScoreboardManager().ifPresent(s -> s.recalculateVanishForPlayer(tabPlayer, target, false));
|
||||
} else {
|
||||
if (!player.getTabList().containsEntry(p.getUniqueId())) {
|
||||
createEntry(target, player.getTabList()).thenAccept(e -> {
|
||||
player.getTabList().addEntry(e);
|
||||
plugin.getScoreboardManager().ifPresent(s -> s.recalculateVanishForPlayer(tabPlayer, target, true));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
122
src/main/java/net/william278/velocitab/tab/TabListListener.java
Normal file
122
src/main/java/net/william278/velocitab/tab/TabListListener.java
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.tab;
|
||||
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.config.Group;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The TabListListener class is responsible for handling events related to the player tab list.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class TabListListener {
|
||||
|
||||
private final Velocitab plugin;
|
||||
private final PlayerTabList tabList;
|
||||
|
||||
public TabListListener(@NotNull Velocitab plugin, @NotNull PlayerTabList tabList) {
|
||||
this.plugin = plugin;
|
||||
this.tabList = tabList;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onKick(KickedFromServerEvent event) {
|
||||
event.getPlayer().getTabList().clearAll();
|
||||
event.getPlayer().getTabList().clearHeaderAndFooter();
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@Subscribe
|
||||
public void onPlayerJoin(@NotNull ServerPostConnectEvent event) {
|
||||
final Player joined = event.getPlayer();
|
||||
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(joined));
|
||||
|
||||
final String serverName = joined.getCurrentServer()
|
||||
.map(ServerConnection::getServerInfo)
|
||||
.map(ServerInfo::getName)
|
||||
.orElse("");
|
||||
final Group group = tabList.getGroup(serverName);
|
||||
final boolean isDefault = !group.servers().contains(serverName);
|
||||
|
||||
// If the server is not in a group, use fallback.
|
||||
// If fallback is disabled, permit the player to switch excluded servers without a header or footer override
|
||||
if (isDefault && !plugin.getSettings().isFallbackEnabled()) {
|
||||
event.getPlayer().sendPlayerListHeaderAndFooter(Component.empty(), Component.empty());
|
||||
tabList.getPlayers().remove(event.getPlayer().getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
tabList.joinPlayer(joined, group);
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
public void onPlayerQuit(@NotNull DisconnectEvent event) {
|
||||
if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the player from the tracking list, Print warning if player was not removed
|
||||
final UUID uuid = event.getPlayer().getUniqueId();
|
||||
final TabPlayer tabPlayer = tabList.getPlayers().get(uuid);
|
||||
if (tabPlayer == null) {
|
||||
plugin.log(String.format("Failed to remove disconnecting player %s (UUID: %s)",
|
||||
event.getPlayer().getUsername(), uuid));
|
||||
}
|
||||
|
||||
// Remove the player from the tab list of all other players
|
||||
plugin.getServer().getAllPlayers().forEach(player -> player.getTabList().removeEntry(uuid));
|
||||
|
||||
// Update the tab list of all players
|
||||
plugin.getServer().getScheduler()
|
||||
.buildTask(plugin, () -> tabList.getPlayers().values().forEach(player -> {
|
||||
player.getPlayer().getTabList().removeEntry(uuid);
|
||||
player.sendHeaderAndFooter(tabList);
|
||||
}))
|
||||
.delay(500, TimeUnit.MILLISECONDS)
|
||||
.schedule();
|
||||
// Delete player team
|
||||
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(event.getPlayer()));
|
||||
//remove player from tab list cache
|
||||
tabList.getPlayers().remove(uuid);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void proxyReload(@NotNull ProxyReloadEvent event) {
|
||||
plugin.loadConfigs();
|
||||
tabList.reloadUpdate();
|
||||
plugin.log("Velocitab has been reloaded!");
|
||||
}
|
||||
|
||||
}
|
119
src/main/java/net/william278/velocitab/tab/VanishTabList.java
Normal file
119
src/main/java/net/william278/velocitab/tab/VanishTabList.java
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* This file is part of Velocitab, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.velocitab.tab;
|
||||
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* The VanishTabList handles the tab list for vanished players
|
||||
*/
|
||||
public class VanishTabList {
|
||||
|
||||
private final Velocitab plugin;
|
||||
private final PlayerTabList tabList;
|
||||
|
||||
public VanishTabList(Velocitab plugin, PlayerTabList tabList) {
|
||||
this.plugin = plugin;
|
||||
this.tabList = tabList;
|
||||
}
|
||||
|
||||
|
||||
public void vanishPlayer(@NotNull TabPlayer tabPlayer) {
|
||||
tabList.getPlayers().values().forEach(p -> {
|
||||
if (p.getPlayer().equals(tabPlayer.getPlayer())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!plugin.getVanishManager().canSee(p.getPlayer().getUsername(), tabPlayer.getPlayer().getUsername())) {
|
||||
p.getPlayer().getTabList().removeEntry(tabPlayer.getPlayer().getUniqueId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void unVanishPlayer(@NotNull TabPlayer tabPlayer) {
|
||||
final UUID uuid = tabPlayer.getPlayer().getUniqueId();
|
||||
|
||||
tabPlayer.getDisplayName(plugin).thenAccept(c -> tabList.getPlayers().values().forEach(p -> {
|
||||
if (p.getPlayer().equals(tabPlayer.getPlayer())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!p.getPlayer().getTabList().containsEntry(uuid)) {
|
||||
p.getPlayer().getTabList().addEntry(tabList.createEntry(tabPlayer, p.getPlayer().getTabList(), c));
|
||||
} else {
|
||||
p.getPlayer().getTabList().getEntry(uuid).ifPresent(entry -> entry.setDisplayName(c));
|
||||
}
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculates the visibility of players in the tab list for the given player.
|
||||
* If tabPlayer can see the player, the player will be added to the tab list.
|
||||
*
|
||||
* @param tabPlayer The TabPlayer object representing the player for whom to recalculate the tab list visibility.
|
||||
*/
|
||||
public void recalculateVanishForPlayer(@NotNull TabPlayer tabPlayer) {
|
||||
final Player player = tabPlayer.getPlayer();
|
||||
final List<String> serversInGroup = tabPlayer.getGroup().servers();
|
||||
|
||||
plugin.getServer().getAllPlayers().forEach(p -> {
|
||||
if (p.equals(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<TabPlayer> targetOptional = tabList.getTabPlayer(p);
|
||||
if (targetOptional.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TabPlayer target = targetOptional.get();
|
||||
final String serverName = target.getServerName();
|
||||
|
||||
if (plugin.getSettings().isOnlyListPlayersInSameGroup()
|
||||
&& !serversInGroup.contains(serverName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean canSee = !plugin.getVanishManager().isVanished(p.getUsername()) ||
|
||||
plugin.getVanishManager().canSee(player.getUsername(), p.getUsername());
|
||||
|
||||
if (!canSee) {
|
||||
player.getTabList().removeEntry(p.getUniqueId());
|
||||
plugin.getScoreboardManager().ifPresent(s -> s.recalculateVanishForPlayer(tabPlayer, target, false));
|
||||
} else {
|
||||
if (!player.getTabList().containsEntry(p.getUniqueId())) {
|
||||
tabList.createEntry(target, player.getTabList()).thenAccept(e -> {
|
||||
player.getTabList().addEntry(e);
|
||||
plugin.getScoreboardManager().ifPresent(s -> s.recalculateVanishForPlayer(tabPlayer, target, true));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -60,8 +60,8 @@ public class VanishManager {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getTabList().vanishPlayer(tabPlayer.get());
|
||||
plugin.getScoreboardManager().ifPresent(scoreboardManager -> scoreboardManager.vanishPlayer(player));
|
||||
plugin.getTabList().getVanishTabList().vanishPlayer(tabPlayer.get());
|
||||
plugin.getScoreboardManager().ifPresent(scoreboardManager -> scoreboardManager.vanishPlayer(tabPlayer.get()));
|
||||
}
|
||||
|
||||
public void unVanishPlayer(@NotNull Player player) {
|
||||
@ -71,7 +71,7 @@ public class VanishManager {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getTabList().unVanishPlayer(tabPlayer.get());
|
||||
plugin.getScoreboardManager().ifPresent(scoreboardManager -> scoreboardManager.unVanishPlayer(player));
|
||||
plugin.getTabList().getVanishTabList().unVanishPlayer(tabPlayer.get());
|
||||
plugin.getScoreboardManager().ifPresent(scoreboardManager -> scoreboardManager.unVanishPlayer(tabPlayer.get()));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user