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
@NotNull
public Velocitab getPlugin() {
return this;
}

View File

@ -23,6 +23,7 @@ import de.exlll.configlib.NameFormatters;
import de.exlll.configlib.YamlConfigurationProperties;
import de.exlll.configlib.YamlConfigurations;
import net.william278.desertwell.util.Version;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@ -42,8 +43,13 @@ public interface ConfigProvider {
@NotNull
YamlConfigurationProperties.Builder<?> YAML_CONFIGURATION_PROPERTIES = YamlConfigurationProperties.newBuilder()
.charset(StandardCharsets.UTF_8)
.outputNulls(true)
.inputNulls(false)
.setNameFormatter(NameFormatters.LOWER_UNDERSCORE);
@NotNull
Velocitab getPlugin();
/**
* Get the plugin settings, read from the config file
*
@ -72,7 +78,7 @@ public interface ConfigProvider {
Settings.class,
YAML_CONFIGURATION_PROPERTIES.header(Settings.CONFIG_HEADER).build()
));
getSettings().validateConfig();
getSettings().validateConfig(getPlugin());
}
/**
@ -103,7 +109,7 @@ public interface ConfigProvider {
TabGroups.class,
YAML_CONFIGURATION_PROPERTIES.header(TabGroups.CONFIG_HEADER).build()
));
getTabGroups().validateConfig();
getTabGroups().validateConfig(getPlugin());
}
/**
@ -131,6 +137,23 @@ public interface ConfigProvider {
@NotNull
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
*

View File

@ -19,12 +19,15 @@
package net.william278.velocitab.config;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
public interface ConfigValidator {
/**
* Validates the configuration settings.
* @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.server.RegisteredServer;
import de.exlll.configlib.Comment;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.player.TabPlayer;
import net.william278.velocitab.tab.Nametag;
@ -32,6 +31,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("unused")
public record Group(
String name,
List<String> headers,
@ -40,10 +40,7 @@ public record Group(
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.""")
boolean collisions,
int headerFooterUpdateRate,
int placeholderUpdateRate
) {

View File

@ -24,6 +24,7 @@ import de.exlll.configlib.Configuration;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
@ -97,7 +98,9 @@ public class Settings implements ConfigValidator{
}
@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;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import de.exlll.configlib.Configuration;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.tab.Nametag;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@SuppressWarnings("FieldMayBeFinal")
@ -42,20 +47,21 @@ public class TabGroups implements ConfigValidator {
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
)
private static final Group DEFAULT_GROUP = 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%"),
false,
1000,
1000
);
public List<Group> groups = List.of(DEFAULT_GROUP);
@NotNull
public Group getGroupFromName(@NotNull String name) {
return groups.stream()
@ -80,12 +86,68 @@ public class TabGroups implements ConfigValidator {
@Override
public void validateConfig() {
public void validateConfig(@NotNull Velocitab plugin) {
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");
}
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())
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
.collisionRule(CollisionRule.ALWAYS)
.collisionRule(tabPlayer.getGroup().collisions() ? CollisionRule.ALWAYS : CollisionRule.NEVER)
.color(getLastColor(nametag.prefix(), plugin))
.prefix(nametag.getPrefixComponent(plugin, tabPlayer))
.suffix(nametag.getSuffixComponent(plugin, tabPlayer))
@ -100,7 +100,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
.displayName(Component.empty())
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
.collisionRule(CollisionRule.ALWAYS)
.collisionRule(tabPlayer.getGroup().collisions() ? CollisionRule.ALWAYS : CollisionRule.NEVER)
.color(getLastColor(nametag.prefix(), plugin))
.prefix(nametag.getPrefixComponent(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 footerIndex = 0;
private Component lastDisplayName;
private Component lastHeader;
private Component lastFooter;
private String teamName;
@Nullable
@Setter
@ -127,7 +129,11 @@ public final class TabPlayer implements Comparable<TabPlayer> {
public CompletableFuture<Void> sendHeaderAndFooter(@NotNull PlayerTabList tabList) {
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() {

View File

@ -31,8 +31,10 @@ 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.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -76,12 +78,24 @@ 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()) {
final Component header = event.getPlayer().getPlayerListHeader();
final Component footer = event.getPlayer().getPlayerListFooter();
final Optional<TabPlayer> tabPlayer = tabList.getTabPlayer(joined);
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, () -> {
if (header.equals(event.getPlayer().getPlayerListHeader()) && footer.equals(event.getPlayer().getPlayerListFooter())) {
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();