mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d6206211c | ||
|
|
c8aef50f4b | ||
|
|
4745301c18 | ||
|
|
b9f0abe751 | ||
|
|
025e23aedd | ||
|
|
83334b97f2 | ||
|
|
c51d9898ce | ||
|
|
4b116a80bc | ||
|
|
a0eb00c3c2 | ||
|
|
186d5cebcd | ||
|
|
d9bfbba565 | ||
|
|
49afdcc156 | ||
|
|
6e3c1e61df | ||
|
|
d13b83a6d6 | ||
|
|
f9cc5b79dd | ||
|
|
06733e5a12 | ||
|
|
31c5fdf387 | ||
|
|
d440a28164 | ||
|
|
5b08bd70c1 | ||
|
|
22a7a8a76a | ||
|
|
a46f75b8be |
No files matched your search
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.2.0"><title>Release: 6.2.0</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="275" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">Release</text><text x="275" y="150" fill-opacity=".3" textLength="430">Release</text></g><text x="275" y="140" textLength="430">Release</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="715" y="150" fill-opacity=".8" filter="url(#blur)" textLength="290">6.2.0</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.2.0</text></g><text x="715" y="140" textLength="290">6.2.0</text></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.2.1"><title>Release: 6.2.1</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="275" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">Release</text><text x="275" y="150" fill-opacity=".3" textLength="430">Release</text></g><text x="275" y="140" textLength="430">Release</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="715" y="150" fill-opacity=".8" filter="url(#blur)" textLength="290">6.2.1</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.2.1</text></g><text x="715" y="140" textLength="290">6.2.1</text></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -274,7 +274,7 @@ if __name__ == "__main__":
|
||||
]
|
||||
)
|
||||
|
||||
release = "6.2.0"
|
||||
release = "6.2.1"
|
||||
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
|
||||
|
||||
java = "8+"
|
||||
|
||||
@@ -51,15 +51,15 @@ belowname-objective:
|
||||
```
|
||||
All the options are explained in the following table.
|
||||
|
||||
| Option name | Default value | Description |
|
||||
|---------------------|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, where it completely replaces `value`. When evaluates to an empty string, the entire belowname is hidden for viewers using MC 26.2+ (when it was made possible). |
|
||||
| fancy-value-default | NPC | [1.20.3 - 26.1.2] Default number format (`fancy-value`) for all player entities. `fancy-value` is displayed on every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on since 1.20.3 (the function did not exist prior) until 26.1.2 (player entities without scores no longer display any belowname since 26.2). |
|
||||
| title | Health | Shared label shown after the score for every player entity. Player sees the same text on everyone (= placeholders are parsed for the viewer). Use the `value`/`fancy-value` fields for per‑player data; use `title` only for static labels like `Health` or placeholders which are supposed to be parsed for the viewing player. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) (either name of a condition or a conditional expression) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. <br/> **Note**: Disabling the feature for a player means sending objective unregister packet to them, which results in player not seeing belowname on anyone anymore. It doesn't work the other way around - you cannot disable this feature on target players, only for viewers. |
|
||||
| view-distance | 10 | [26.1+] How close (in blocks) to a player one must be to see their belowname. This value is configurable since Minecraft 26.1, so this option won't work on older versions. |
|
||||
| Option name | Default value | Description |
|
||||
|---------------------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, where it completely replaces `value`. When evaluates to an empty string, the entire belowname is hidden for viewers using MC 26.2+ (when it was made possible). |
|
||||
| fancy-value-default | NPC | [1.20.3 - 26.1.2] Default number format (`fancy-value`) for all player entities. `fancy-value` is displayed on every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on since 1.20.3 (the function did not exist prior) until 26.1.2 (player entities without scores no longer display any belowname since 26.2). |
|
||||
| title | Health | Shared label shown after the score for every player entity. Player sees the same text on everyone (= placeholders are parsed for the viewer). Use the `value`/`fancy-value` fields for per‑player data; use `title` only for static labels like `Health` or placeholders which are supposed to be parsed for the viewing player. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) (either name of a condition or a conditional expression) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. <br/> **Note**: Disabling the feature for a player means sending objective unregister packet to them, which results in player not seeing belowname on anyone anymore. It doesn't work the other way around - you cannot disable this feature on target players, only for viewers (at least not until 26.2+, in which case see `fancy-value` description to see how to do it). |
|
||||
| view-distance | 10 | [26.1+] How close (in blocks) to a player one must be to see their belowname. This value is configurable since Minecraft 26.1, so this option won't work on older versions. |
|
||||
|
||||
# Limitations
|
||||
* [1.12.2-] Title length is limited to 32 characters (including color codes).
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Chaining conditional placeholders](#example-1---chaining-conditional-placeholders)
|
||||
* [Example 2 - Combining AND and OR](#example-2---combining-and-and-or)
|
||||
* [Example 3 - Showing vanish status only for staff members](#example-3---showing-vanish-status-only-for-staff-members)
|
||||
|
||||
# About
|
||||
Conditions / conditional placeholders allow you
|
||||
@@ -216,3 +217,26 @@ conditions:
|
||||
type: AND
|
||||
```
|
||||
Then, use condition `main` as the display condition (or as a placeholder - `%condition:main%`). Note that `true`/`false` values were not defined, as such, they default to `true` and `false`, respectively. Therefore, we use the placeholder from the condition and check if the result is `true`. Then, check if player is also in the specified server.
|
||||
|
||||
## Example 3 - Showing vanish status only for staff members
|
||||
This example shows you how to show some text on vanished players, but only available to see to staff members. For that, we will use a relational condition.
|
||||
We want to create a condition that checks for 2 things - target player is vanished and the viewer has permission. If both of them are met (`AND`), some text should display. Otherwise, nothing should display.
|
||||
Let's build the individual components of the condition:
|
||||
* Vanish placeholder - in this example, we will use `%essentials_vanished%`. The placeholder returns `yes` if player is vanished, otherwise `no`. If you configured [placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements) for the vanish placeholder, you will either need to use the replaced output, or remove the replacement (since you aren't going to use the placeholder directly anyway). To check what exactly a placeholder returns, you can run `/tab parse <player> <placeholder>`.
|
||||
* Staff check - for the lack of a better idea, let's name our permission `my.permission`. The important part is that we want to check this permission for the player who is looking at the tablist, not the target player who is vanished. Following the guide on relational placeholders, we will use `%viewer:player_has_permission_my.permission%`. We need to compare it to the "true" value, (typically `yes`), just like with vanish placeholder. See above for more info.
|
||||
* true / false texts - The `true` value will be set to the value to show if target player is vanished and viewer has the permission. `false` value will be displayed otherwise. This will probably remain empty.
|
||||
|
||||
After combining all of this, here is what the condition may look like:
|
||||
```
|
||||
conditions:
|
||||
vanish:
|
||||
conditions:
|
||||
- '%viewer:player_has_permission_my.permission%=yes'
|
||||
- '%essentials_vanished%=yes'
|
||||
type: AND
|
||||
true: ' &7&l[V]&r'
|
||||
false: ''
|
||||
```
|
||||
You may replace the leading space in `true` value with a trailing one based on your needs. Afterwards, use the placeholder using `%rel_condition:vanish%` in `tabprefix` / `tabsuffix` for example.
|
||||
|
||||
The usefulness of this example is questionable, but the purpose of examples is to teach afterall, not to just be copied over to your config (I know you have been doing that!). This one is, however, useful, because unvanishing will result in TAB instantly formatting the player name before placeholders are periodically updated, which would otherwise result in a small flash of vanished text visible for everyone for a split second after unvanishing.
|
||||
@@ -1,5 +1,5 @@
|
||||
# About the wiki
|
||||
The wiki explains the functionality of the plugin as of version **6.2.0**.
|
||||
The wiki explains the functionality of the plugin as of version **6.2.1**.
|
||||
If your config looks different or is missing some features, you are using an old version of the plugin.
|
||||
To get the latest version, check [releases](https://github.com/NEZNAMY/TAB/releases/).
|
||||
Wiki for older plugin versions is not available.
|
||||
|
||||
@@ -28,31 +28,8 @@ Another option is to configure prefixes per-group in config
|
||||
instead of using placeholders to take those values from your permission plugin.
|
||||
|
||||
## #2 - [All platforms] Placeholder refresh intervals
|
||||
Some placeholders take a long time to process and result in high cpu usage. However, this can be optimized by configuring refresh interval of placeholders.
|
||||
|
||||
Internal placeholders use a value hardcoded into the plugin by default. PlaceholderAPI placeholders use `default-refresh-interval`.
|
||||
|
||||
To begin, find this part in your config
|
||||
```yml
|
||||
placeholder-refresh-intervals:
|
||||
default-refresh-interval: 500
|
||||
"%server_uptime%": 1000
|
||||
"%server_tps_1_colored%": 1000
|
||||
"%player_health%": 200
|
||||
"%player_ping%": 1000
|
||||
"%vault_prefix%": 1000
|
||||
"%rel_factionsuuid_relation_color%": 500
|
||||
```
|
||||
Refresh interval is in milliseconds. It must be divisible by 50.
|
||||
|
||||
`default-refresh-interval` is refresh interval for all unlisted PlaceholderAPI placeholders.
|
||||
|
||||
Keep in mind that CPU measurement is reset every 10 seconds.
|
||||
This means that if you configure a higher refresh interval than 10000,
|
||||
there will be intervals when placeholder didn't refresh at all,
|
||||
making it look like the CPU is perfect, while on the next refresh it may be high.
|
||||
For this reason, if you want to cleanly track usage, keep the interval at 10000 at most.
|
||||
Using a higher interval will help if needed, but may lead to misleading info in cpu output.
|
||||
Some placeholders take a long time to process and result in high cpu usage. However, this can be optimized by configuring refresh interval of placeholders.
|
||||
See [Placeholders - Refreshing](https://github.com/NEZNAMY/TAB/wiki/Placeholders#refreshing) for information about modifying refresh intervals of placeholders.
|
||||
|
||||
## #3 - [All platforms] Don't use RGB
|
||||
Though RGB colors look good, they require the text to be split into several components with `color` field used instead of the whole text pasted into the `text` field. This is a complicated process that takes quite some time. Sticking to legacy colors results in better performance.
|
||||
|
||||
@@ -157,13 +157,13 @@ On **Velocity**, TAB integrates with [MiniPlaceholders](https://github.com/MiniP
|
||||
Unlike classic placeholders which take either 0
|
||||
(placeholders with the same output for all players) or 1 (per-player placeholders) players,
|
||||
relational placeholders take in 2 players.
|
||||
The first usage was to display relation between two players, specifically enemies / allies (red / green) for factions,
|
||||
The first ever recorded usage was to display relation between two players, specifically enemies / allies (red / green) for factions,
|
||||
allowing players to see faction names of other players colored based on relation.
|
||||
While the usage is not limited to this, the name of the 2-player placeholder type has been established as "relational"
|
||||
and will be called that everywhere.
|
||||
and will be called that everywhere. These placeholders only make sense in functions that allow 2 players to see each other - chat, nametags (+ belowname), tablist formatting (+ objective). Other places, such as scoreboard, bossbar or header/footer don't make sense and don't support these placeholders, as there is no other player to use.
|
||||
|
||||
## Usage
|
||||
TAB does not offer any internal relational placeholders. However, it has full support for PlaceholderAPI placeholders. You can also register your own relational placeholders using the [API](#relational-placeholders-1). In PlaceholderAPI, all relational placeholder identifiers must start with `%rel_`. This rule is adopted and used in TAB as well (including those registered via API).
|
||||
TAB offers [relational conditions](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders#relational-conditions) as well as full support for PlaceholderAPI placeholders. You can also register your own relational placeholders using the [API](#relational-placeholders-1). In PlaceholderAPI, all relational placeholder identifiers must start with `%rel_`. This rule is adopted and used in TAB as well (including those registered via API).
|
||||
|
||||
When passing players into the function, first player is the player viewing the text, second player is the one text is being displayed on.
|
||||
Code wise, the PlaceholderAPI request function becomes
|
||||
@@ -177,7 +177,27 @@ To properly distinguish viewer/target (if needed), your relational placeholder e
|
||||
|
||||
# Refreshing
|
||||
## Refresh intervals
|
||||
TAB is refreshing placeholders in intervals. For internal placeholders it's using values I found the most optimal and set directly in the plugin. Refresh intervals for PlaceholderAPI placeholders can be [configured](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals).
|
||||
Internal placeholders use a value hardcoded into the plugin by default. PlaceholderAPI placeholders use `default-refresh-interval` by default.
|
||||
|
||||
To begin, find this part in your config
|
||||
```yml
|
||||
placeholder-refresh-intervals:
|
||||
default-refresh-interval: 500
|
||||
"%server_uptime%": 1000
|
||||
"%server_tps_1_colored%": 1000
|
||||
"%player_health%": 200
|
||||
"%player_ping%": 1000
|
||||
"%vault_prefix%": 1000
|
||||
"%rel_factionsuuid_relation_color%": 500
|
||||
```
|
||||
Refresh interval is in milliseconds. It must be divisible by 50.
|
||||
|
||||
Keep in mind that CPU measurement is reset every 10 seconds.
|
||||
This means that if you configure a higher refresh interval than 10000,
|
||||
there will be intervals when placeholder didn't refresh at all,
|
||||
making it look like the CPU is perfect, while on the next refresh it may be high.
|
||||
For this reason, if you want to cleanly track usage, keep the interval at 10000 at most.
|
||||
Using a higher interval will help if needed, but may lead to misleading info in cpu output.
|
||||
|
||||
## Sync refreshing
|
||||
By default, all placeholders are refreshed asynchronously,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
compileOnlyApi("org.jetbrains:annotations:26.0.2")
|
||||
compileOnlyApi("org.jetbrains:annotations:26.1.0")
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
|
||||
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.4.2")
|
||||
implementation("io.freefair.gradle:lombok-plugin:9.5.0")
|
||||
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.6.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:9.8.3")
|
||||
implementation("dev.architectury.loom-no-remap:dev.architectury.loom-no-remap.gradle.plugin:1.17-SNAPSHOT")
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "6.2.1"
|
||||
version = "6.2.2-SNAPSHOT"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies {
|
||||
api(projects.shared)
|
||||
implementation("org.bstats:bstats-bukkit:3.1.0")
|
||||
implementation("org.bstats:bstats-bukkit:3.2.1")
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
|
||||
compileOnly("me.clip:placeholderapi:2.11.6")
|
||||
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
|
||||
|
||||
@@ -9,8 +9,10 @@ import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.cpu.TimedCaughtTask;
|
||||
import me.neznamy.tab.shared.data.World;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.platform.LineOfSightCheck;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
@@ -21,8 +23,15 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Platform override for Folia.
|
||||
@@ -36,6 +45,11 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
/** FoliaGlobalRegionScheduler#execute(Plugin, Runnable) method */
|
||||
private final Method globalScheduler_execute;
|
||||
|
||||
/** Location overload avoids accessing a target entity from the viewer's region. */
|
||||
private final Method hasLineOfSightLocation;
|
||||
|
||||
private final AtomicBoolean lineOfSightErrorLogged = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
*
|
||||
@@ -48,6 +62,7 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
super(plugin);
|
||||
globalScheduler = Bukkit.class.getMethod("getGlobalRegionScheduler").invoke(null);
|
||||
globalScheduler_execute = globalScheduler.getClass().getMethod("execute", Plugin.class, Runnable.class);
|
||||
hasLineOfSightLocation = Player.class.getMethod("hasLineOfSight", Location.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,8 +187,93 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
globalScheduler_execute.invoke(globalScheduler, getPlugin(), task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures target positions on their owning regions, then ray traces from each viewer's
|
||||
* owning region. No target entity is read while running a viewer's check.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasLineOfSight(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return true; // Cross-entity line-of-sight checks are not safe from Folia's global scheduler.
|
||||
public void requestLineOfSight(@NotNull List<LineOfSightCheck> checks, @NotNull Consumer<boolean[]> callback) {
|
||||
boolean[] visible = new boolean[checks.size()];
|
||||
if (checks.isEmpty() || !getPlugin().isEnabled()) {
|
||||
callback.accept(visible);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<TabPlayer, CompletableFuture<Location>> targetLocations = new IdentityHashMap<>();
|
||||
for (LineOfSightCheck check : checks) {
|
||||
targetLocations.computeIfAbsent(check.getTarget(), target -> {
|
||||
CompletableFuture<Location> location = new CompletableFuture<>();
|
||||
scheduleEntity((Player) target.getPlayer(), () -> {
|
||||
try {
|
||||
Player player = (Player) target.getPlayer();
|
||||
location.complete(player.isOnline() ? player.getEyeLocation() : null);
|
||||
} catch (RuntimeException e) {
|
||||
location.complete(null);
|
||||
logLineOfSightError(e);
|
||||
}
|
||||
}, () -> location.complete(null));
|
||||
return location;
|
||||
});
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(targetLocations.values().toArray(new CompletableFuture[0])).whenComplete((ignored, error) -> {
|
||||
Map<TabPlayer, List<Integer>> checksByViewer = new IdentityHashMap<>();
|
||||
for (int i = 0; i < checks.size(); i++) {
|
||||
checksByViewer.computeIfAbsent(checks.get(i).getViewer(), viewer -> new ArrayList<>()).add(i);
|
||||
}
|
||||
|
||||
List<CompletableFuture<Void>> viewerTasks = new ArrayList<>(checksByViewer.size());
|
||||
for (Map.Entry<TabPlayer, List<Integer>> entry : checksByViewer.entrySet()) {
|
||||
CompletableFuture<Void> finished = new CompletableFuture<>();
|
||||
viewerTasks.add(finished);
|
||||
scheduleEntity((Player) entry.getKey().getPlayer(), () -> {
|
||||
try {
|
||||
Player viewer = (Player) entry.getKey().getPlayer();
|
||||
if (viewer.isOnline()) {
|
||||
for (int index : entry.getValue()) {
|
||||
Location target = targetLocations.get(checks.get(index).getTarget()).getNow(null);
|
||||
if (target == null || target.getWorld() != viewer.getWorld()) continue;
|
||||
try {
|
||||
visible[index] = (boolean) hasLineOfSightLocation.invoke(viewer, target);
|
||||
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||
visible[index] = false;
|
||||
logLineOfSightError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
for (int index : entry.getValue()) visible[index] = false;
|
||||
logLineOfSightError(e);
|
||||
} finally {
|
||||
finished.complete(null);
|
||||
}
|
||||
}, () -> finished.complete(null));
|
||||
}
|
||||
CompletableFuture.allOf(viewerTasks.toArray(new CompletableFuture[0]))
|
||||
.whenComplete((done, failure) -> callback.accept(visible));
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleEntity(@NotNull Player player, @NotNull Runnable task, @NotNull Runnable retired) {
|
||||
if (!getPlugin().isEnabled()) {
|
||||
retired.run();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Object scheduler = Entity.class.getMethod("getScheduler").invoke(player);
|
||||
Consumer<?> consumer = ignored -> task.run();
|
||||
Object scheduled = scheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
|
||||
.invoke(scheduler, getPlugin(), consumer, retired);
|
||||
if (scheduled == null) retired.run();
|
||||
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||
retired.run();
|
||||
logLineOfSightError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logLineOfSightError(@NotNull Exception error) {
|
||||
if (lineOfSightErrorLogged.compareAndSet(false, true)) {
|
||||
getPlugin().getLogger().log(Level.WARNING, "Unable to check opaque nametag line of sight on Folia", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bungeecord:3.1.0")
|
||||
implementation("org.bstats:bstats-bungeecord:3.2.1")
|
||||
compileOnly("net.md-5:bungeecord-api:26.1-R0.1-SNAPSHOT")
|
||||
compileOnly("net.md-5:bungeecord-proxy:1.21-R0.4-SNAPSHOT")
|
||||
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
plugins {
|
||||
id("net.kyori.blossom") version "2.1.0"
|
||||
id("net.kyori.blossom") version "2.2.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.api)
|
||||
api("org.yaml:snakeyaml:2.0")
|
||||
api("org.yaml:snakeyaml:2.7")
|
||||
api("com.github.NEZNAMY:yamlassist:1.0.8")
|
||||
api("com.googlecode.json-simple:json-simple:1.1.1") {
|
||||
exclude("junit", "junit")
|
||||
@@ -14,13 +14,13 @@ dependencies {
|
||||
exclude("org.checkerframework", "checker-qual")
|
||||
}
|
||||
api("com.mysql:mysql-connector-j:9.6.0")
|
||||
compileOnlyApi("com.viaversion:viaversion-api:5.2.1")
|
||||
compileOnlyApi("com.viaversion:viaversion-api:5.12.2")
|
||||
compileOnlyApi("io.netty:netty-all:4.1.90.Final")
|
||||
compileOnlyApi("net.luckperms:api:5.4")
|
||||
compileOnlyApi("net.luckperms:api:5.5")
|
||||
compileOnlyApi("com.google.guava:guava:31.1-jre")
|
||||
compileOnlyApi("org.geysermc.floodgate:api:2.2.0-SNAPSHOT")
|
||||
compileOnlyApi("net.kyori:adventure-api:4.25.0-SNAPSHOT")
|
||||
compileOnlyApi("net.kyori:adventure-text-minimessage:4.25.0-SNAPSHOT")
|
||||
compileOnlyApi("net.kyori:adventure-api:4.26.1")
|
||||
compileOnlyApi("net.kyori:adventure-text-minimessage:4.26.1")
|
||||
implementation("com.saicone.delivery4j:delivery4j:1.1.1")
|
||||
implementation("com.saicone.delivery4j:broker-rabbitmq:1.1.1") {
|
||||
exclude("org.slf4j", "slf4j-api")
|
||||
@@ -38,7 +38,7 @@ sourceSets.main {
|
||||
property("name", rootProject.name)
|
||||
property("id", rootProject.ext.get("id")!!.toString())
|
||||
property("version", project.version.toString())
|
||||
property("description", project.description)
|
||||
property("description", project.description.orEmpty())
|
||||
property("website", rootProject.ext.get("website")!!.toString())
|
||||
property("author", rootProject.ext.get("author")!!.toString())
|
||||
property("credits", rootProject.ext.get("credits")!!.toString())
|
||||
|
||||
@@ -85,6 +85,7 @@ public class TabConstants {
|
||||
public static final String COMMAND_PROPERTY_CHANGE_PREFIX = "tab.change.";
|
||||
|
||||
public static final String COMMAND_BOSSBAR_ANNOUNCE = "tab.announce.bar";
|
||||
public static final String COMMAND_BOSSBAR_SEND = "tab.send.bar";
|
||||
public static final String COMMAND_BOSSBAR_TOGGLE = "tab.bossbar.toggle";
|
||||
public static final String COMMAND_BOSSBAR_TOGGLE_OTHER = "tab.bossbar.toggle.other";
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public class BossBarSendCommand extends SubCommand {
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BossBarSendCommand() {
|
||||
super("send", TabConstants.Permission.COMMAND_SCOREBOARD_SHOW);
|
||||
super("send", TabConstants.Permission.COMMAND_BOSSBAR_SEND);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,7 +46,7 @@ public class BossBarSendCommand extends SubCommand {
|
||||
try {
|
||||
duration = Integer.parseInt(args[2]);
|
||||
} catch (NumberFormatException e) {
|
||||
sendMessage(sender, getMessages().getInvalidNumber(args[1]));
|
||||
sendMessage(sender, getMessages().getInvalidNumber(args[2]));
|
||||
return;
|
||||
}
|
||||
BossBar bar = feature.getBossBar(barName);
|
||||
|
||||
@@ -512,15 +512,16 @@ public class LegacyConverter {
|
||||
* Config file
|
||||
*/
|
||||
public void convert507to510(@NonNull ConfigurationFile config) {
|
||||
if (config.rename("enable-redisbungee-support", "proxy-support.enabled")) {
|
||||
config.rename("enable-redisbungee-support", "proxy-support.enabled");
|
||||
config.setIfMissing("proxy-support.enabled", true);
|
||||
config.setIfMissing("proxy-support.type", "PLUGIN");
|
||||
config.setIfMissing("proxy-support.plugin.name", "RedisBungee");
|
||||
config.setIfMissing("proxy-support.redis.url", "redis://:password@localhost:6379/0");
|
||||
config.setIfMissing("proxy-support.rabbitmq.exchange", "plugin");
|
||||
config.setIfMissing("proxy-support.rabbitmq.url", "amqp://guest:guest@localhost:5672/%2F");
|
||||
if (config.rename("placeholderapi-refresh-intervals", "placeholder-refresh-intervals")) {
|
||||
TAB.getInstance().getPlatform().logInfo(new TabTextComponent("Performing configuration conversion from 5.0.7 to 5.1.0", TabTextColor.YELLOW));
|
||||
config.set("proxy-support.type", "PLUGIN");
|
||||
config.set("proxy-support.plugin.name", "RedisBungee");
|
||||
config.set("proxy-support.redis.url", "redis://:password@localhost:6379/0");
|
||||
config.set("proxy-support.rabbitmq.exchange", "plugin");
|
||||
config.set("proxy-support.rabbitmq.url", "amqp://guest:guest@localhost:5672/%2F");
|
||||
}
|
||||
config.rename("placeholderapi-refresh-intervals", "placeholder-refresh-intervals");
|
||||
config.setIfMissing("playerlist-objective.title", "TAB");
|
||||
config.setIfMissing("playerlist-objective.render-type", Arrays.asList("%health%", "%player_health%", "%player_health_rounded%").contains(config.getString("playerlist-objective.value", "")) ? "HEARTS" : "INTEGER");
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ public class LayoutConfiguration {
|
||||
ConfigurationSection groupSection = defaultSkins.getConfigurationSection(asString);
|
||||
|
||||
String skin = groupSection.getString("skin");
|
||||
for (String line : section.getStringList("slots", Collections.emptyList())) {
|
||||
if (skin == null) continue;
|
||||
for (String line : groupSection.getStringList("slots", Collections.emptyList())) {
|
||||
String[] arr = line.split("-");
|
||||
int from = Integer.parseInt(arr[0]);
|
||||
int to = arr.length == 1 ? from : Integer.parseInt(arr[1]);
|
||||
|
||||
@@ -86,6 +86,7 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
|
||||
@Override
|
||||
public void onQuit(@NotNull TabPlayer p) {
|
||||
sortedPlayers.remove(p);
|
||||
p.layoutData.currentLayout = null;
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (all == p) continue;
|
||||
if (all.layoutData.currentLayout != null) all.layoutData.currentLayout.view.tick();
|
||||
|
||||
+13
-2
@@ -122,7 +122,7 @@ public class NameTagProxyPlayerData extends ProxyMessage {
|
||||
// Property update
|
||||
if (viewer.teamData.hasTeamRegistered(target)) {
|
||||
viewer.getScoreboard().updateTeam(
|
||||
oldData.teamName,
|
||||
oldData.resolvedTeamName,
|
||||
prefix,
|
||||
suffix,
|
||||
nameVisibility,
|
||||
@@ -165,7 +165,18 @@ public class NameTagProxyPlayerData extends ProxyMessage {
|
||||
if (!nameTaken && feature.getProxy() != null) {
|
||||
for (ProxyPlayer all : feature.getProxy().getProxyPlayers().values()) {
|
||||
if (all == player) continue;
|
||||
if (all.getNametag() != null && potentialTeamName.equals(all.getNametag().teamName)) {
|
||||
NameTagProxyPlayerData nametag = all.getNametag();
|
||||
if (nametag != null && (potentialTeamName.equals(nametag.teamName) || potentialTeamName.equals(nametag.resolvedTeamName))) {
|
||||
nameTaken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!nameTaken && feature.getProxy() != null) {
|
||||
// Players still queued have a resolved name that will be used once they are known
|
||||
for (QueuedData queued : feature.getProxy().getQueuedData().values()) {
|
||||
NameTagProxyPlayerData nametag = queued.getNametag();
|
||||
if (nametag != null && nametag != this && !nametag.playerId.equals(playerId) && potentialTeamName.equals(nametag.resolvedTeamName)) {
|
||||
nameTaken = true;
|
||||
break;
|
||||
}
|
||||
|
||||
+35
-30
@@ -11,6 +11,7 @@ import me.neznamy.tab.shared.features.types.JoinListener;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
import me.neznamy.tab.shared.features.types.RefreshableFeature;
|
||||
import me.neznamy.tab.shared.placeholders.conditions.Condition;
|
||||
import me.neznamy.tab.shared.platform.LineOfSightCheck;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -21,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Sub-feature for NameTag feature that manages nametag visibility.
|
||||
@@ -39,6 +41,10 @@ public class VisibilityManager extends RefreshableFeature implements JoinListene
|
||||
@NotNull
|
||||
private final Set<TabPlayer> opaqueNameTagViewers = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
/** Prevents an older asynchronous occlusion check from overwriting a newer result. */
|
||||
private final AtomicLong opaqueRefreshSequence = new AtomicLong();
|
||||
private long lastAppliedOpaqueRefresh;
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
@@ -271,30 +277,41 @@ public class VisibilityManager extends RefreshableFeature implements JoinListene
|
||||
}
|
||||
|
||||
private void requestOpaqueOcclusionRefresh() {
|
||||
List<OpaqueVisibilityCheck> checks = new ArrayList<>();
|
||||
List<LineOfSightCheck> checks = new ArrayList<>();
|
||||
List<OpaqueVisibilityResult> immediateResults = new ArrayList<>();
|
||||
for (TabPlayer player : nameTags.getOnlinePlayers().getPlayers()) {
|
||||
if (!hasOpaqueNameTagMode(player) || player.teamData.isDisabled()) continue;
|
||||
for (TabPlayer viewer : nameTags.getOnlinePlayers().getPlayers()) {
|
||||
checks.add(new OpaqueVisibilityCheck(player, viewer, !isOpaqueNameTagMode(player, viewer)));
|
||||
if (!isOpaqueNameTagMode(player, viewer) || viewer == player || !viewer.isOnline() || !player.isOnline()) {
|
||||
immediateResults.add(new OpaqueVisibilityResult(player, viewer, true));
|
||||
} else if (!canSeeForOpaque(viewer, player)) {
|
||||
immediateResults.add(new OpaqueVisibilityResult(player, viewer, false));
|
||||
} else {
|
||||
checks.add(new LineOfSightCheck(viewer, player));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checks.isEmpty()) return;
|
||||
TAB.getInstance().getPlatform().runSyncGlobal(() -> {
|
||||
List<OpaqueVisibilityResult> results = new ArrayList<>(checks.size());
|
||||
for (OpaqueVisibilityCheck check : checks) {
|
||||
if (!check.player.isOnline() || !check.viewer.isOnline()) {
|
||||
results.add(new OpaqueVisibilityResult(check.player, check.viewer, true));
|
||||
continue;
|
||||
}
|
||||
results.add(new OpaqueVisibilityResult(check.player, check.viewer,
|
||||
check.forceVisible || check.viewer == check.player || hasLineOfSight(check.viewer, check.player)));
|
||||
if (checks.isEmpty() && immediateResults.isEmpty()) return;
|
||||
long sequence = opaqueRefreshSequence.incrementAndGet();
|
||||
if (checks.isEmpty()) {
|
||||
getCustomThread().execute(new TimedCaughtTask(TAB.getInstance().getCpu(),
|
||||
() -> applyOpaqueOcclusionResults(immediateResults, sequence), getFeatureName(), "Applying opaque nametag occlusion"));
|
||||
return;
|
||||
}
|
||||
TAB.getInstance().getPlatform().requestLineOfSight(checks, visible -> {
|
||||
List<OpaqueVisibilityResult> results = new ArrayList<>(immediateResults);
|
||||
for (int i = 0; i < checks.size(); i++) {
|
||||
LineOfSightCheck check = checks.get(i);
|
||||
results.add(new OpaqueVisibilityResult(check.getTarget(), check.getViewer(), visible[i]));
|
||||
}
|
||||
getCustomThread().execute(new TimedCaughtTask(TAB.getInstance().getCpu(),
|
||||
() -> applyOpaqueOcclusionResults(results), getFeatureName(), "Applying opaque nametag occlusion"));
|
||||
() -> applyOpaqueOcclusionResults(results, sequence), getFeatureName(), "Applying opaque nametag occlusion"));
|
||||
});
|
||||
}
|
||||
|
||||
private void applyOpaqueOcclusionResults(@NonNull List<OpaqueVisibilityResult> results) {
|
||||
private void applyOpaqueOcclusionResults(@NonNull List<OpaqueVisibilityResult> results, long sequence) {
|
||||
if (sequence <= lastAppliedOpaqueRefresh) return;
|
||||
lastAppliedOpaqueRefresh = sequence;
|
||||
for (OpaqueVisibilityResult result : results) {
|
||||
if (!nameTags.getOnlinePlayers().contains(result.player) || !nameTags.getOnlinePlayers().contains(result.viewer)) continue;
|
||||
boolean visible = result.visible;
|
||||
@@ -302,6 +319,8 @@ public class VisibilityManager extends RefreshableFeature implements JoinListene
|
||||
visible = true;
|
||||
} else if (!isOpaqueNameTagMode(result.player, result.viewer)) {
|
||||
visible = true;
|
||||
} else if (!canSeeForOpaque(result.viewer, result.player)) {
|
||||
visible = false;
|
||||
}
|
||||
boolean changed = visible
|
||||
? result.player.teamData.showNametag(result.viewer, NameTagInvisibilityReason.OPAQUE_OCCLUSION)
|
||||
@@ -333,24 +352,10 @@ public class VisibilityManager extends RefreshableFeature implements JoinListene
|
||||
return player.teamData.isOpaqueNameTagMode(viewer) || opaqueNameTagViewers.contains(viewer);
|
||||
}
|
||||
|
||||
private boolean hasLineOfSight(@NonNull TabPlayer viewer, @NonNull TabPlayer target) {
|
||||
private boolean canSeeForOpaque(@NonNull TabPlayer viewer, @NonNull TabPlayer target) {
|
||||
if (!viewer.server.canSee(target.server)) return false;
|
||||
if (viewer.world != target.world) return false;
|
||||
if (!viewer.canSee(target)) return false;
|
||||
return TAB.getInstance().getPlatform().hasLineOfSight(viewer, target);
|
||||
}
|
||||
|
||||
private static class OpaqueVisibilityCheck {
|
||||
|
||||
@NotNull private final TabPlayer player;
|
||||
@NotNull private final TabPlayer viewer;
|
||||
private final boolean forceVisible;
|
||||
|
||||
private OpaqueVisibilityCheck(@NotNull TabPlayer player, @NotNull TabPlayer viewer, boolean forceVisible) {
|
||||
this.player = player;
|
||||
this.viewer = viewer;
|
||||
this.forceVisible = forceVisible;
|
||||
}
|
||||
return viewer.canSee(target);
|
||||
}
|
||||
|
||||
private static class OpaqueVisibilityResult {
|
||||
|
||||
@@ -34,8 +34,9 @@ public class QueuedData {
|
||||
@Nullable
|
||||
private NameTagProxyPlayerData nametag;
|
||||
|
||||
/** Whether player is vanished or not */
|
||||
private boolean vanished;
|
||||
/** Whether player is vanished or not, {@code null} if no vanish status update was received */
|
||||
@Nullable
|
||||
private Boolean vanished;
|
||||
|
||||
/** Name of server the player is connected to */
|
||||
@NotNull
|
||||
|
||||
@@ -87,7 +87,7 @@ public class PlayerJoin extends ProxyMessage {
|
||||
decodedPlayer.setTabFormat(data.getTabFormat());
|
||||
decodedPlayer.setNametag(data.getNametag());
|
||||
decodedPlayer.setPlayerlist(data.getPlayerlist());
|
||||
decodedPlayer.setVanished(data.isVanished());
|
||||
if (data.getVanished() != null) decodedPlayer.setVanished(data.getVanished());
|
||||
}
|
||||
if (TAB.getInstance().getPlayer(decodedPlayer.getUniqueId()) == null) {
|
||||
TAB.getInstance().getFeatureManager().onJoin(decodedPlayer);
|
||||
|
||||
+2
-2
@@ -314,7 +314,7 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
|
||||
throw new IllegalArgumentException("No registered scoreboard found with name " + name);
|
||||
}
|
||||
|
||||
Set<TabPlayer> players = removed.getPlayers();
|
||||
Set<TabPlayer> players = new HashSet<>(removed.getPlayers()); // Copy, unregister() clears the set
|
||||
removed.unregister();
|
||||
for (TabPlayer p : players) {
|
||||
p.scoreboardData.forcedScoreboard = null;
|
||||
@@ -328,7 +328,7 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
|
||||
throw new IllegalArgumentException("This scoreboard (" + scoreboard.getName() + ") is not registered.");
|
||||
}
|
||||
|
||||
Set<TabPlayer> players = ((ScoreboardImpl)scoreboard).getPlayers();
|
||||
Set<TabPlayer> players = new HashSet<>(((ScoreboardImpl)scoreboard).getPlayers()); // Copy, unregister() clears the set
|
||||
scoreboard.unregister();
|
||||
for (TabPlayer p : players) {
|
||||
p.scoreboardData.forcedScoreboard = null;
|
||||
|
||||
+5
-16
@@ -90,26 +90,15 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromNested(@NonNull TabPlayer viewer) {
|
||||
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
|
||||
public void updateFromNested(@NonNull TabPlayer target) {
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
String value = request(viewer, target);
|
||||
String s = replacements.findReplacement(String.valueOf(value));
|
||||
viewer.lastRelationalValues.computeIfAbsent(this, v -> Collections.synchronizedMap(new WeakHashMap<>())).put(target, s);
|
||||
if (!target.isLoaded()) continue; // Updated on join
|
||||
for (RefreshableFeature f : reference.getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> f.refresh(target, true),
|
||||
f.getFeatureName(), f.getRefreshDisplayName());
|
||||
if (f instanceof CustomThreaded) {
|
||||
((CustomThreaded) f).getCustomThread().execute(task);
|
||||
} else {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
updateParents(target);
|
||||
}
|
||||
if (!viewer.isLoaded()) return; // Updated on join
|
||||
if (!target.isLoaded()) return; // Updated on join
|
||||
for (RefreshableFeature f : reference.getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> f.refresh(viewer, true),
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> f.refresh(target, true),
|
||||
f.getFeatureName(), f.getRefreshDisplayName());
|
||||
if (f instanceof CustomThreaded) {
|
||||
((CustomThreaded) f).getCustomThread().execute(task);
|
||||
@@ -117,7 +106,7 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
updateParents(viewer);
|
||||
updateParents(target);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
|
||||
if (hasValueChanged(value)) {
|
||||
for (RefreshableFeature r : reference.getUsedByFeatures()) {
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (!all.isLoaded()) return; // Updated on join
|
||||
if (!all.isLoaded()) continue; // Updated on join
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> r.refresh(all, false), r.getFeatureName(), r.getRefreshDisplayName());
|
||||
if (r instanceof CustomThreaded) {
|
||||
((CustomThreaded) r).getCustomThread().execute(task);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.neznamy.tab.shared.platform;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A viewer and target whose block line of sight must be checked.
|
||||
* Results are returned in the same order as the submitted checks.
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class LineOfSightCheck {
|
||||
|
||||
@NotNull private final TabPlayer viewer;
|
||||
@NotNull private final TabPlayer target;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An interface with methods that are called in universal code,
|
||||
@@ -277,4 +278,21 @@ public interface Platform {
|
||||
default boolean hasLineOfSight(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks block line of sight without assuming that all entities share a server thread.
|
||||
* The callback receives one result per input check, in the same order. Platforms with
|
||||
* region-owned entities can override this to complete the checks asynchronously.
|
||||
*/
|
||||
default void requestLineOfSight(@NotNull List<LineOfSightCheck> checks, @NotNull Consumer<boolean[]> callback) {
|
||||
runSyncGlobal(() -> {
|
||||
boolean[] results = new boolean[checks.size()];
|
||||
for (int i = 0; i < checks.size(); i++) {
|
||||
LineOfSightCheck check = checks.get(i);
|
||||
results[i] = !check.getViewer().isOnline() || !check.getTarget().isOnline() ||
|
||||
hasLineOfSight(check.getViewer(), check.getTarget());
|
||||
}
|
||||
callback.accept(results);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,9 @@ public abstract class TabPlayer implements me.neznamy.tab.api.TabPlayer {
|
||||
if (!VanishIntegration.getHandlers().isEmpty()) {
|
||||
try {
|
||||
for (VanishIntegration i : VanishIntegration.getHandlers()) {
|
||||
if (!i.canSee(this, target)) return false;
|
||||
// if (!i.canSee(this, target)) return false;
|
||||
// #1739 - Vanish integration should have an absolute say
|
||||
return i.canSee(this, target);
|
||||
}
|
||||
} catch (ConcurrentModificationException e) {
|
||||
// PV error, try again
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-velocity:3.1.0")
|
||||
implementation("org.bstats:bstats-velocity:3.2.1")
|
||||
compileOnly("com.github.limework.redisbungee:RedisBungee-Velocity:0.11.0")
|
||||
compileOnly("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT")
|
||||
compileOnly("com.velocitypowered:velocity-proxy:3.4.0-SNAPSHOT")
|
||||
compileOnlyApi("net.kyori:adventure-nbt:4.17.0")
|
||||
annotationProcessor("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT")
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.9.0-4")
|
||||
compileOnly("net.william278:velocityscoreboardapi:2.0.0")
|
||||
compileOnly("net.william278:velocityscoreboardapi:2.1.1")
|
||||
compileOnly("io.github.miniplaceholders:miniplaceholders-api:3.1.0")
|
||||
}
|
||||
|
||||
|
||||
+16
-13
@@ -15,18 +15,11 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* The core for Velocity forwarding events into all enabled features
|
||||
*/
|
||||
public class VelocityEventListener implements EventListener<Player> {
|
||||
|
||||
/** Map for tracking online players */
|
||||
private final Map<Player, UUID> players = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Listens to player disconnecting from the server.
|
||||
*
|
||||
@@ -35,11 +28,16 @@ public class VelocityEventListener implements EventListener<Player> {
|
||||
*/
|
||||
@Subscribe
|
||||
public void onQuit(@NotNull DisconnectEvent e) {
|
||||
if (TAB.getInstance().isPluginDisabled()) return;
|
||||
// Check if the player was actually connected to the server in the first place to avoid processing
|
||||
// disconnect of an existing player who is still there (because players are mapped by UUID in TAB)
|
||||
UUID id = players.remove(e.getPlayer());
|
||||
if (id != null) quit(id);
|
||||
TAB tab = TAB.getInstance();
|
||||
if (tab.isPluginDisabled()) return;
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
// Only process the disconnect if the loaded player belongs to this exact connection to avoid processing
|
||||
// disconnect of a different session with the same UUID that is still connected (because players are mapped by UUID in TAB)
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player != null && player.getPlayer() == e.getPlayer()) {
|
||||
tab.getFeatureManager().onQuit(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,8 +71,13 @@ public class VelocityEventListener implements EventListener<Player> {
|
||||
if (tab.isPluginDisabled()) return;
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player != null && player.getPlayer() != e.getPlayer()) {
|
||||
// Player from a previous session with the same UUID whose disconnect was not processed yet, remove it
|
||||
tab.getFeatureManager().onQuit(player);
|
||||
player = null;
|
||||
}
|
||||
if (player == null) {
|
||||
players.put(e.getPlayer(), e.getPlayer().getUniqueId());
|
||||
if (!e.getPlayer().isActive()) return; // Player disconnected before this task ran, do not create a ghost player
|
||||
tab.getFeatureManager().onJoin(createPlayer(e.getPlayer()));
|
||||
} else {
|
||||
if (!(player.getScoreboard() instanceof VelocityScoreboard)) player.getScoreboard().resend();
|
||||
|
||||
Reference in new issue
Block a user