Improve config validator, add team collision rule setting (#152)

* Fixed tab problem on not handled servers

* Fixed config validator and added collisions parameter

* Fixed conversations
This commit is contained in:
AlexDev_ 2024-02-02 00:03:40 +01:00 committed by GitHub
parent 63ed22527b
commit 7caa185fc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 137 additions and 28 deletions

View File

@ -137,6 +137,7 @@ public class Velocitab implements ConfigProvider, ScoreboardProvider, LoggerProv
} }
@Override @Override
@NotNull
public Velocitab getPlugin() { public Velocitab getPlugin() {
return this; return this;
} }

View File

@ -23,6 +23,7 @@ import de.exlll.configlib.NameFormatters;
import de.exlll.configlib.YamlConfigurationProperties; import de.exlll.configlib.YamlConfigurationProperties;
import de.exlll.configlib.YamlConfigurations; import de.exlll.configlib.YamlConfigurations;
import net.william278.desertwell.util.Version; import net.william278.desertwell.util.Version;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
@ -42,8 +43,13 @@ public interface ConfigProvider {
@NotNull @NotNull
YamlConfigurationProperties.Builder<?> YAML_CONFIGURATION_PROPERTIES = YamlConfigurationProperties.newBuilder() YamlConfigurationProperties.Builder<?> YAML_CONFIGURATION_PROPERTIES = YamlConfigurationProperties.newBuilder()
.charset(StandardCharsets.UTF_8) .charset(StandardCharsets.UTF_8)
.outputNulls(true)
.inputNulls(false)
.setNameFormatter(NameFormatters.LOWER_UNDERSCORE); .setNameFormatter(NameFormatters.LOWER_UNDERSCORE);
@NotNull
Velocitab getPlugin();
/** /**
* Get the plugin settings, read from the config file * Get the plugin settings, read from the config file
* *
@ -72,7 +78,7 @@ public interface ConfigProvider {
Settings.class, Settings.class,
YAML_CONFIGURATION_PROPERTIES.header(Settings.CONFIG_HEADER).build() YAML_CONFIGURATION_PROPERTIES.header(Settings.CONFIG_HEADER).build()
)); ));
getSettings().validateConfig(); getSettings().validateConfig(getPlugin());
} }
/** /**
@ -103,7 +109,7 @@ public interface ConfigProvider {
TabGroups.class, TabGroups.class,
YAML_CONFIGURATION_PROPERTIES.header(TabGroups.CONFIG_HEADER).build() YAML_CONFIGURATION_PROPERTIES.header(TabGroups.CONFIG_HEADER).build()
)); ));
getTabGroups().validateConfig(); getTabGroups().validateConfig(getPlugin());
} }
/** /**
@ -131,6 +137,23 @@ public interface ConfigProvider {
@NotNull @NotNull
Version getVelocityVersion(); Version getVelocityVersion();
/**
* Saves the tab groups to the "tab_groups.yml" config file.
* Uses the YamlConfigurations.save method to write the tab groups object to the specified config file path.
* This method assumes that the getConfigDirectory method returns a valid directory path.
*
* @throws IllegalStateException if the getConfigDirectory method returns null
* @since 1.0
*/
default void saveTabGroups() {
YamlConfigurations.save(
getConfigDirectory().resolve("tab_groups.yml"),
TabGroups.class,
getTabGroups(),
YAML_CONFIGURATION_PROPERTIES.header(TabGroups.CONFIG_HEADER).build()
);
}
/** /**
* Get the plugin config directory * Get the plugin config directory
* *

View File

@ -19,12 +19,15 @@
package net.william278.velocitab.config; package net.william278.velocitab.config;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
public interface ConfigValidator { public interface ConfigValidator {
/** /**
* Validates the configuration settings. * Validates the configuration settings.
* @throws IllegalStateException if the configuration is invalid * @throws IllegalStateException if the configuration is invalid
*/ */
void validateConfig() throws IllegalStateException; void validateConfig(@NotNull Velocitab plugin) throws IllegalStateException;
} }

View File

@ -21,7 +21,6 @@ package net.william278.velocitab.config;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import de.exlll.configlib.Comment;
import net.william278.velocitab.Velocitab; import net.william278.velocitab.Velocitab;
import net.william278.velocitab.player.TabPlayer; import net.william278.velocitab.player.TabPlayer;
import net.william278.velocitab.tab.Nametag; import net.william278.velocitab.tab.Nametag;
@ -32,6 +31,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@SuppressWarnings("unused")
public record Group( public record Group(
String name, String name,
List<String> headers, List<String> headers,
@ -40,10 +40,7 @@ public record Group(
Nametag nametag, Nametag nametag,
List<String> servers, List<String> servers,
List<String> sortingPlaceholders, List<String> sortingPlaceholders,
@Comment(""" boolean collisions,
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 headerFooterUpdateRate,
int placeholderUpdateRate int placeholderUpdateRate
) { ) {

View File

@ -24,6 +24,7 @@ import de.exlll.configlib.Configuration;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Map; import java.util.Map;
@ -97,7 +98,9 @@ public class Settings implements ConfigValidator{
} }
@Override @Override
public void validateConfig() { public void validateConfig(@NotNull Velocitab plugin) {
if (papiCacheTime < 0) {
throw new IllegalStateException("PAPI cache time must be greater than or equal to 0");
}
} }
} }

View File

@ -19,13 +19,18 @@
package net.william278.velocitab.config; package net.william278.velocitab.config;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import de.exlll.configlib.Configuration; import de.exlll.configlib.Configuration;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.tab.Nametag; import net.william278.velocitab.tab.Nametag;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
@SuppressWarnings("FieldMayBeFinal") @SuppressWarnings("FieldMayBeFinal")
@ -42,20 +47,21 @@ public class TabGroups implements ConfigValidator {
Information: https://william278.net/project/velocitab Information: https://william278.net/project/velocitab
Documentation: https://william278.net/docs/velocitab"""; Documentation: https://william278.net/docs/velocitab""";
public List<Group> groups = List.of( private static final Group DEFAULT_GROUP = new Group(
new Group( "default",
"default", List.of("&rainbow&Running Velocitab by William278"),
List.of("&rainbow&Running Velocitab by William278"), List.of("[There are currently %players_online%/%max_players_online% players online](gray)"),
List.of("[There are currently %players_online%/%max_players_online% players online](gray)"), "&7[%server%] &f%prefix%%username%",
"&7[%server%] &f%prefix%%username%", new Nametag("&f%prefix%", "&f%suffix%"),
new Nametag("&f%prefix%", "&f%suffix%"), List.of("lobby", "survival", "creative", "minigames", "skyblock", "prison", "hub"),
List.of("lobby", "survival", "creative", "minigames", "skyblock", "prison", "hub"), List.of("%role_weight%", "%username_lower%"),
List.of("%role_weight%", "%username_lower%"), false,
1000, 1000,
1000 1000
)
); );
public List<Group> groups = List.of(DEFAULT_GROUP);
@NotNull @NotNull
public Group getGroupFromName(@NotNull String name) { public Group getGroupFromName(@NotNull String name) {
return groups.stream() return groups.stream()
@ -80,12 +86,68 @@ public class TabGroups implements ConfigValidator {
@Override @Override
public void validateConfig() { public void validateConfig(@NotNull Velocitab plugin) {
if (groups.isEmpty()) { if (groups.isEmpty()) {
throw new IllegalStateException("No tab groups defined in config"); throw new IllegalStateException("No tab groups defined in config");
} }
if (groups.stream().noneMatch(group -> group.name().equals("default"))) { if (groups.stream().noneMatch(group -> group.name().equals("default"))) {
throw new IllegalStateException("No default tab group defined in config"); throw new IllegalStateException("No default tab group defined in config");
} }
final Multimap<Group, String> missingKeys = getMissingKeys();
if (missingKeys.isEmpty()) {
return;
}
fixMissingKeys(plugin, missingKeys);
}
@NotNull
private Multimap<Group, String> getMissingKeys() {
final Multimap<Group, String> missingKeys = Multimaps.newSetMultimap(new HashMap<>(), HashSet::new);
for (Group group : groups) {
if (group.format() == null) {
missingKeys.put(group, "format");
}
if (group.nametag() == null) {
missingKeys.put(group, "nametag");
}
if (group.servers() == null) {
missingKeys.put(group, "servers");
}
if (group.sortingPlaceholders() == null) {
missingKeys.put(group, "sortingPlaceholders");
}
}
return missingKeys;
}
private void fixMissingKeys(@NotNull Velocitab plugin, @NotNull Multimap<Group, String> missingKeys) {
missingKeys.forEach((group, keys) -> {
plugin.log("Missing required key(s) " + keys + " for group " + group.name());
plugin.log("Using default values for group " + group.name());
groups.remove(group);
group = new Group(
group.name(),
group.headers(),
group.footers(),
group.format() == null ? DEFAULT_GROUP.format() : group.format(),
group.nametag() == null ? DEFAULT_GROUP.nametag() : group.nametag(),
group.servers() == null ? DEFAULT_GROUP.servers() : group.servers(),
group.sortingPlaceholders() == null ? DEFAULT_GROUP.sortingPlaceholders() : group.sortingPlaceholders(),
group.collisions(),
group.headerFooterUpdateRate(),
group.placeholderUpdateRate()
);
groups.add(group);
});
plugin.saveTabGroups();
} }
} }

View File

@ -75,7 +75,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
.displayName(Component.empty()) .displayName(Component.empty())
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY)) .friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER) .nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
.collisionRule(CollisionRule.ALWAYS) .collisionRule(tabPlayer.getGroup().collisions() ? CollisionRule.ALWAYS : CollisionRule.NEVER)
.color(getLastColor(nametag.prefix(), plugin)) .color(getLastColor(nametag.prefix(), plugin))
.prefix(nametag.getPrefixComponent(plugin, tabPlayer)) .prefix(nametag.getPrefixComponent(plugin, tabPlayer))
.suffix(nametag.getSuffixComponent(plugin, tabPlayer)) .suffix(nametag.getSuffixComponent(plugin, tabPlayer))
@ -100,7 +100,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
.displayName(Component.empty()) .displayName(Component.empty())
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY)) .friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER) .nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
.collisionRule(CollisionRule.ALWAYS) .collisionRule(tabPlayer.getGroup().collisions() ? CollisionRule.ALWAYS : CollisionRule.NEVER)
.color(getLastColor(nametag.prefix(), plugin)) .color(getLastColor(nametag.prefix(), plugin))
.prefix(nametag.getPrefixComponent(plugin, tabPlayer)) .prefix(nametag.getPrefixComponent(plugin, tabPlayer))
.suffix(nametag.getSuffixComponent(plugin, tabPlayer)); .suffix(nametag.getSuffixComponent(plugin, tabPlayer));

View File

@ -44,6 +44,8 @@ public final class TabPlayer implements Comparable<TabPlayer> {
private int headerIndex = 0; private int headerIndex = 0;
private int footerIndex = 0; private int footerIndex = 0;
private Component lastDisplayName; private Component lastDisplayName;
private Component lastHeader;
private Component lastFooter;
private String teamName; private String teamName;
@Nullable @Nullable
@Setter @Setter
@ -127,7 +129,11 @@ public final class TabPlayer implements Comparable<TabPlayer> {
public CompletableFuture<Void> sendHeaderAndFooter(@NotNull PlayerTabList tabList) { public CompletableFuture<Void> sendHeaderAndFooter(@NotNull PlayerTabList tabList) {
return tabList.getHeader(this).thenCompose(header -> tabList.getFooter(this) return tabList.getHeader(this).thenCompose(header -> tabList.getFooter(this)
.thenAccept(footer -> player.sendPlayerListHeaderAndFooter(header, footer))); .thenAccept(footer -> {
lastHeader = header;
lastFooter = footer;
player.sendPlayerListHeaderAndFooter(header, footer);
}));
} }
public void incrementIndexes() { public void incrementIndexes() {

View File

@ -31,8 +31,10 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.william278.velocitab.Velocitab; import net.william278.velocitab.Velocitab;
import net.william278.velocitab.config.Group; import net.william278.velocitab.config.Group;
import net.william278.velocitab.player.TabPlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -76,12 +78,24 @@ public class TabListListener {
// If the server is not in a group, use fallback. // 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 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()) {
final Component header = event.getPlayer().getPlayerListHeader(); final Optional<TabPlayer> tabPlayer = tabList.getTabPlayer(joined);
final Component footer = event.getPlayer().getPlayerListFooter(); if (tabPlayer.isEmpty()) {
return;
}
final Component header = tabPlayer.get().getLastHeader();
final Component footer = tabPlayer.get().getLastFooter();
final Component displayName = tabPlayer.get().getLastDisplayName();
plugin.getServer().getScheduler().buildTask(plugin, () -> { plugin.getServer().getScheduler().buildTask(plugin, () -> {
if (header.equals(event.getPlayer().getPlayerListHeader()) && footer.equals(event.getPlayer().getPlayerListFooter())) { if (header.equals(event.getPlayer().getPlayerListHeader()) && footer.equals(event.getPlayer().getPlayerListFooter())) {
event.getPlayer().sendPlayerListHeaderAndFooter(header, footer); event.getPlayer().sendPlayerListHeaderAndFooter(header, footer);
event.getPlayer().getCurrentServer().ifPresent(serverConnection ->
serverConnection.getServer().getPlayersConnected().forEach(player ->
player.getTabList().getEntry(joined.getUniqueId()).ifPresent(entry -> {
if (entry.getDisplayNameComponent().isPresent() && entry.getDisplayNameComponent().get().equals(displayName)) {
entry.setDisplayName(Component.text(joined.getUsername()));
}
})));
} }
}).delay(500, TimeUnit.MILLISECONDS).schedule(); }).delay(500, TimeUnit.MILLISECONDS).schedule();