forked from Upstream/Velocitab
feat: add plugin message api, GROUP_PLAYERS_ONLINE placeholder (#157)
* Added plugin message api & added LOCAL_GROUP_PLAYERS_ONLINE placeholders * Fixed conversations, added placeholders to docs and fixed a few bugs * Solved conversation * Fixed possible charset problem and moved channels to a map instead of a set * Changed docs * Fixed kick issue and fixed problem header/footer on join
This commit is contained in:
parent
a5940e0315
commit
4efc5797b3
@ -2,6 +2,8 @@ The Velocitab API provides methods for vanishing ("hiding") and modifying userna
|
||||
|
||||
The API is distributed on Maven through [repo.william278.net](https://repo.william278.net/#/releases/net/william278/velocitab/) and can be included in any Maven, Gradle, etc. project. JavaDocs are [available here](https://repo.william278.net/javadoc/releases/net/william278/velocitab/latest).
|
||||
|
||||
Velocitab also provides a plugin message API, which is documented in the [[Plugin Message API Examples]] page.
|
||||
|
||||
## Compatibility
|
||||
[](https://repo.william278.net/#/releases/net/william278/velocitab/)
|
||||
|
||||
|
@ -18,6 +18,7 @@ Please click through to the topic you wish to read about.
|
||||
* 🖼️ [[Custom Logos]]
|
||||
* 📦 [[API]]
|
||||
* 📝 [[API Examples]]
|
||||
* 📝 [[Plugin Message API Examples]]
|
||||
|
||||
## Links
|
||||
* 💻 [GitHub](https://github.com/WiIIiam278/Velocitab)
|
||||
|
@ -3,26 +3,28 @@ Velocitab supports a number of Placeholders that will be replaced with their res
|
||||
## Default placeholders
|
||||
Placeholders can be included in the header, footer and player name format of the TAB list. The following placeholders are supported out of the box:
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|--------------------------|------------------------------------------------------|--------------------|
|
||||
| `%players_online%` | Players online on the proxy | `6` |
|
||||
| `%max_players_online%` | Player capacity of the proxy | `500` |
|
||||
| `%local_players_online%` | Players online on the server the player is on | `3` |
|
||||
| `%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]` |
|
||||
| `%suffix%` | The player's suffix (from LuckPerms) | `&c ` |
|
||||
| `%role%` | The player's primary LuckPerms group name | `admin` |
|
||||
| `%role_display_name%` | The player's primary LuckPerms group display name | `Admin` |
|
||||
| `%role_weight%` | Comparable-formatted primary LuckPerms group weight. | `100` |
|
||||
| `%luckperms_meta_(key)%` | Formats a meta key from the user's LuckPerms group | (varies) |
|
||||
| `%server_group%` | The name of the server group the player is on | `default` |
|
||||
| `%server_group_index%` | Indexed order of the server group in the list | `0` |
|
||||
| `%debug_team_name%` | (Debug) Player's team name, used for [[Sorting]] | `1alphaWilliam278` |
|
||||
| Placeholder | Description | Example |
|
||||
|---------------------------------|------------------------------------------------------|--------------------|
|
||||
| `%players_online%` | Players online on the proxy | `6` |
|
||||
| `%max_players_online%` | Player capacity of the proxy | `500` |
|
||||
| `%local_players_online%` | Players online on the server the player is on | `3` |
|
||||
| `%group_players_online_(name)%` | Players online on the group provided | `11` |
|
||||
| `%group_players_online%` | Players online on player's group | `15` |
|
||||
| `%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]` |
|
||||
| `%suffix%` | The player's suffix (from LuckPerms) | `&c ` |
|
||||
| `%role%` | The player's primary LuckPerms group name | `admin` |
|
||||
| `%role_display_name%` | The player's primary LuckPerms group display name | `Admin` |
|
||||
| `%role_weight%` | Comparable-formatted primary LuckPerms group weight. | `100` |
|
||||
| `%luckperms_meta_(key)%` | Formats a meta key from the user's LuckPerms group | (varies) |
|
||||
| `%server_group%` | The name of the server group the player is on | `default` |
|
||||
| `%server_group_index%` | Indexed order of the server group in the list | `0` |
|
||||
| `%debug_team_name%` | (Debug) Player's team name, used for [[Sorting]] | `1alphaWilliam278` |
|
||||
|
||||
### Customising server display names
|
||||
You can make use of the `server_display_names` feature in `config.yml` to customise how server display name appear when using the `%server%` placeholder. In the below example, if a user is connected to a server with the name "`very-long-server-`name" and the player name format for the group that server belongs to includes a `%server%` placeholder, the placeholder would be replaced with "`VSLN`" instead of the full server name.
|
||||
|
27
docs/Plugin-Message-API-Examples.md
Normal file
27
docs/Plugin-Message-API-Examples.md
Normal file
@ -0,0 +1,27 @@
|
||||
Velocitab provides a plugin message API.
|
||||
|
||||
## API Requests from Backend Plugins
|
||||
|
||||
### 1 Changing player's username in the TAB List
|
||||
To change a player's username in the tablist, you can send a plugin message with the channel `velocitab:main` and as data `UPDATE_CUSTOM_NAME:::customName`.
|
||||
Remember to replace `customName` with the desired name.
|
||||
<details>
|
||||
<summary>Example — Changing player's username in the TAB List</summary>
|
||||
|
||||
```java
|
||||
player.sendPluginMessage(plugin, "velocitab:update_custom_name", "Steve".getBytes());
|
||||
```
|
||||
</details>
|
||||
|
||||
### 2 Update team color
|
||||
To change a player's team color in the TAB List, you can send a plugin message with the channel `velocitab:main` and as data `UPDATE_TEAM_COLOR:::teamColor`.
|
||||
You can only use legacy color codes, for example `a` for green, `b` for aqua, etc.
|
||||
This option overrides the glow effect if set
|
||||
|
||||
<details>
|
||||
<summary>Example — Changing player's team color</summary>
|
||||
|
||||
```java
|
||||
player.sendPluginMessage(plugin, "velocitab:update_team_color", "a".getBytes());
|
||||
```
|
||||
</details>
|
@ -34,6 +34,7 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.william278.desertwell.util.UpdateChecker;
|
||||
import net.william278.desertwell.util.Version;
|
||||
import net.william278.velocitab.api.PluginMessageAPI;
|
||||
import net.william278.velocitab.api.VelocitabAPI;
|
||||
import net.william278.velocitab.commands.VelocitabCommand;
|
||||
import net.william278.velocitab.config.ConfigProvider;
|
||||
@ -86,6 +87,7 @@ public class Velocitab implements ConfigProvider, ScoreboardProvider, LoggerProv
|
||||
private SortingManager sortingManager;
|
||||
private VanishManager vanishManager;
|
||||
private PacketEventManager packetEventManager;
|
||||
private PluginMessageAPI pluginMessageAPI;
|
||||
|
||||
@Inject
|
||||
public Velocitab(@NotNull ProxyServer server, @NotNull Logger logger, @DataDirectory Path configDirectory) {
|
||||
@ -114,7 +116,7 @@ public class Velocitab implements ConfigProvider, ScoreboardProvider, LoggerProv
|
||||
server.getScheduler().tasksByPlugin(this).forEach(ScheduledTask::cancel);
|
||||
disableScoreboardManager();
|
||||
getLuckPermsHook().ifPresent(LuckPermsHook::closeEvent);
|
||||
VelocitabAPI.unregister();
|
||||
unregisterAPI();
|
||||
logger.info("Successfully disabled Velocitab");
|
||||
}
|
||||
|
||||
@ -149,6 +151,19 @@ public class Velocitab implements ConfigProvider, ScoreboardProvider, LoggerProv
|
||||
|
||||
private void prepareAPI() {
|
||||
VelocitabAPI.register(this);
|
||||
if (settings.isEnablePluginMessageApi()) {
|
||||
pluginMessageAPI = new PluginMessageAPI(this);
|
||||
pluginMessageAPI.registerChannel();
|
||||
getLogger().info("Registered Velocitab Plugin Message API");
|
||||
}
|
||||
getLogger().info("Registered Velocitab API");
|
||||
}
|
||||
|
||||
private void unregisterAPI() {
|
||||
VelocitabAPI.unregister();
|
||||
if (pluginMessageAPI != null) {
|
||||
pluginMessageAPI.unregisterChannel();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
|
124
src/main/java/net/william278/velocitab/api/PluginMessageAPI.java
Normal file
124
src/main/java/net/william278/velocitab/api/PluginMessageAPI.java
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import net.william278.velocitab.Velocitab;
|
||||
import net.william278.velocitab.packet.UpdateTeamsPacket;
|
||||
import net.william278.velocitab.player.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class PluginMessageAPI {
|
||||
|
||||
private final Velocitab plugin;
|
||||
private final Map<String, MinecraftChannelIdentifier> channels;
|
||||
|
||||
public PluginMessageAPI(@NotNull Velocitab plugin) {
|
||||
this.plugin = plugin;
|
||||
this.channels = Maps.newHashMap();
|
||||
}
|
||||
|
||||
public void registerChannel() {
|
||||
Arrays.stream(PluginMessageRequest.values())
|
||||
.map(PluginMessageRequest::name)
|
||||
.map(s -> s.toLowerCase(Locale.ENGLISH))
|
||||
.forEach(request -> {
|
||||
final String channelName = "velocitab:" + request;
|
||||
final MinecraftChannelIdentifier channel = MinecraftChannelIdentifier.from(channelName);
|
||||
channels.put(channelName, channel);
|
||||
plugin.getServer().getChannelRegistrar().register(channel);
|
||||
});
|
||||
plugin.getServer().getEventManager().register(plugin, PluginMessageEvent.class, this::onPluginMessage);
|
||||
}
|
||||
|
||||
public void unregisterChannel() {
|
||||
channels.forEach((name, channel) -> plugin.getServer().getChannelRegistrar().unregister(channel));
|
||||
}
|
||||
|
||||
private void onPluginMessage(@NotNull PluginMessageEvent pluginMessageEvent) {
|
||||
final Optional<MinecraftChannelIdentifier> channel = Optional.ofNullable(channels.get(pluginMessageEvent.getIdentifier().getId()));
|
||||
if (channel.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!(pluginMessageEvent.getSource() instanceof ServerConnection serverConnection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Player player = serverConnection.getPlayer();
|
||||
final Optional<TabPlayer> optionalTabPlayer = plugin.getTabList().getTabPlayer(player);
|
||||
if (optionalTabPlayer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TabPlayer tabPlayer = optionalTabPlayer.get();
|
||||
if (!tabPlayer.isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<PluginMessageRequest> request = PluginMessageRequest.get(channel.get());
|
||||
if (request.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String data = new String(pluginMessageEvent.getData());
|
||||
handleAPIRequest(tabPlayer, request.get(), data);
|
||||
}
|
||||
|
||||
private void handleAPIRequest(@NotNull TabPlayer tabPlayer, @NotNull PluginMessageRequest request, @NotNull String arg) {
|
||||
switch (request) {
|
||||
case UPDATE_CUSTOM_NAME -> {
|
||||
tabPlayer.setCustomName(arg);
|
||||
plugin.getTabList().updatePlayer(tabPlayer, true);
|
||||
}
|
||||
case UPDATE_TEAM_COLOR -> {
|
||||
final String clean = arg.replaceAll("&", "").replaceAll("§", "");
|
||||
if (clean.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final char colorChar = clean.charAt(0);
|
||||
final Optional<UpdateTeamsPacket.TeamColor> color = Arrays.stream(UpdateTeamsPacket.TeamColor.values())
|
||||
.filter(teamColor -> teamColor.colorChar() == colorChar)
|
||||
.findFirst();
|
||||
color.ifPresent(teamColor -> {
|
||||
tabPlayer.setTeamColor(teamColor);
|
||||
plugin.getTabList().updatePlayer(tabPlayer, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum PluginMessageRequest {
|
||||
UPDATE_CUSTOM_NAME,
|
||||
UPDATE_TEAM_COLOR;
|
||||
|
||||
public static Optional<PluginMessageRequest> get(@NotNull MinecraftChannelIdentifier channelIdentifier) {
|
||||
return Arrays.stream(values())
|
||||
.filter(request -> request.name().equalsIgnoreCase(channelIdentifier.getName()))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -45,6 +45,10 @@ public enum Placeholder {
|
||||
.map(RegisteredServer::getPlayersConnected)
|
||||
.map(players -> Integer.toString(players.size()))
|
||||
.orElse("")),
|
||||
GROUP_PLAYERS_ONLINE_((param, plugin, player) -> plugin.getTabGroups().getGroup(param)
|
||||
.map(group -> Integer.toString(group.getPlayers(plugin).size()))
|
||||
.orElse("Group " + param + " not found")),
|
||||
GROUP_PLAYERS_ONLINE((plugin, player) -> Integer.toString(player.getGroup().getPlayers(plugin).size())),
|
||||
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())),
|
||||
|
@ -90,6 +90,9 @@ public class Settings implements ConfigValidator {
|
||||
@Comment("Remove gamemode spectator effect for other players in the TAB list.")
|
||||
private boolean removeSpectatorEffect = true;
|
||||
|
||||
@Comment("Whether to enable the Plugin Message API (allows backend plugins to perform certain operations)")
|
||||
private boolean enablePluginMessageApi = true;
|
||||
|
||||
/**
|
||||
* Get display name for the server
|
||||
*
|
||||
|
@ -32,6 +32,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@Getter
|
||||
@ -70,6 +71,13 @@ public class TabGroups implements ConfigValidator {
|
||||
.orElseThrow(() -> new IllegalStateException("No group with name " + name + " found"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Optional<Group> getGroup(@NotNull String name) {
|
||||
return groups.stream()
|
||||
.filter(group -> group.name().equals(name))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Group getGroupFromServer(@NotNull String server) {
|
||||
for (Group group : groups) {
|
||||
|
@ -195,6 +195,14 @@ public class ScoreboardManager {
|
||||
return;
|
||||
}
|
||||
|
||||
final Optional<TabPlayer> optionalTabPlayer = plugin.getTabList().getTabPlayer(p);
|
||||
|
||||
if (optionalTabPlayer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final TabPlayer targetTabPlayer = optionalTabPlayer.get();
|
||||
|
||||
// Prevent duplicate packets
|
||||
if (roles.contains(role)) {
|
||||
return;
|
||||
@ -205,7 +213,7 @@ public class ScoreboardManager {
|
||||
final Nametag tag = nametags.get(role);
|
||||
if (tag != null) {
|
||||
final UpdateTeamsPacket packet = UpdateTeamsPacket.create(
|
||||
plugin, tabPlayer, role, tag, p.getUsername()
|
||||
plugin, targetTabPlayer, role, tag, p.getUsername()
|
||||
);
|
||||
dispatchPacket(packet, player);
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
|
||||
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
|
||||
.collisionRule(tabPlayer.getGroup().collisions() ? CollisionRule.ALWAYS : CollisionRule.NEVER)
|
||||
.color(getLastColor(nametag.prefix(), plugin))
|
||||
.color(getLastColor(tabPlayer, nametag.prefix(), plugin))
|
||||
.prefix(nametag.getPrefixComponent(plugin, tabPlayer))
|
||||
.suffix(nametag.getSuffixComponent(plugin, tabPlayer))
|
||||
.entities(Arrays.asList(teamMembers));
|
||||
@ -101,7 +101,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
|
||||
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
|
||||
.collisionRule(tabPlayer.getGroup().collisions() ? CollisionRule.ALWAYS : CollisionRule.NEVER)
|
||||
.color(getLastColor(nametag.prefix(), plugin))
|
||||
.color(getLastColor(tabPlayer, nametag.prefix(), plugin))
|
||||
.prefix(nametag.getPrefixComponent(plugin, tabPlayer))
|
||||
.suffix(nametag.getSuffixComponent(plugin, tabPlayer));
|
||||
}
|
||||
@ -131,7 +131,11 @@ public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
.mode(UpdateMode.REMOVE_TEAM);
|
||||
}
|
||||
|
||||
public static int getLastColor(@Nullable String text, @NotNull Velocitab plugin) {
|
||||
public static int getLastColor(@NotNull TabPlayer tabPlayer, @Nullable String text, @NotNull Velocitab plugin) {
|
||||
if (tabPlayer.getTeamColor() != null) {
|
||||
text = "&" + tabPlayer.getTeamColor().colorChar();
|
||||
}
|
||||
|
||||
if (text == null) {
|
||||
return 15;
|
||||
}
|
||||
@ -177,6 +181,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
ITALIC('f', 20),
|
||||
RESET('r', 21);
|
||||
|
||||
@Getter
|
||||
private final char colorChar;
|
||||
private final int id;
|
||||
|
||||
|
@ -22,10 +22,12 @@ package net.william278.velocitab.player;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
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.packet.UpdateTeamsPacket;
|
||||
import net.william278.velocitab.tab.Nametag;
|
||||
import net.william278.velocitab.tab.PlayerTabList;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
@ -36,6 +38,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
|
||||
private final Player player;
|
||||
@ -49,6 +52,9 @@ public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
private String teamName;
|
||||
@Nullable
|
||||
@Setter
|
||||
private UpdateTeamsPacket.TeamColor teamColor;
|
||||
@Nullable
|
||||
@Setter
|
||||
private String customName;
|
||||
@Nullable
|
||||
@Setter
|
||||
@ -177,19 +183,4 @@ public final class TabPlayer implements Comparable<TabPlayer> {
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof TabPlayer other && player.getUniqueId().equals(other.player.getUniqueId());
|
||||
}
|
||||
|
||||
@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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public class TabListListener {
|
||||
event.getPlayer().getTabList().clearAll();
|
||||
event.getPlayer().getTabList().clearHeaderAndFooter();
|
||||
|
||||
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) {
|
||||
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer || event.getResult() instanceof KickedFromServerEvent.RedirectPlayer) {
|
||||
tabList.removePlayer(event.getPlayer());
|
||||
}
|
||||
}
|
||||
@ -77,7 +77,7 @@ public class TabListListener {
|
||||
|
||||
// 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()) {
|
||||
if (isDefault && !plugin.getSettings().isFallbackEnabled() && event.getPreviousServer() != null) {
|
||||
final Optional<TabPlayer> tabPlayer = tabList.getTabPlayer(joined);
|
||||
if (tabPlayer.isEmpty()) {
|
||||
return;
|
||||
|
Loading…
Reference in New Issue
Block a user