Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dd8621a
Refactor re-serializing `serverBrand` and `fallbackVersionPing` to le…
WouterGritter Jun 17, 2026
1820a63
Refactor `ParsingUtils#parseVariables` -> `PlaceholderSubstitutor#sub…
WouterGritter Jun 17, 2026
36e8c5f
Allow multiple resolvers
WouterGritter Jun 17, 2026
f876694
Early abort
WouterGritter Jun 17, 2026
ff1cbdd
Implement PlaceholderSubstitutor#substitute that works on lists
WouterGritter Jun 17, 2026
ff0a996
Introduce ConnectedPlayer#getJoinedAt and wire it to [Local|Redis]Clu…
WouterGritter Jun 17, 2026
f1d31bd
Implement parsing of complex sample players in the server ping motd, …
WouterGritter Jun 17, 2026
a3b87e0
Remove unused builder
WouterGritter Jun 17, 2026
deae837
Rename to `BrandPlaceholderResolver`
WouterGritter Jun 17, 2026
f76cb61
Introduce `ignore-anonymous-player-request` config option
WouterGritter Jun 18, 2026
16e0107
Remove "Only MiniMessage format is accepted." from config (this is po…
WouterGritter Jun 18, 2026
3479f9b
Resolve checkstyle issues
WouterGritter Jun 18, 2026
bf0b6ce
Introduce `pool-players-across-sections` config option
WouterGritter Jun 18, 2026
0f0abd6
Move config entries around and add migrations for new config entries
WouterGritter Jun 18, 2026
2406365
Update defaults and introduce previously missing `motd-hover` migration
WouterGritter Jun 18, 2026
f582d8f
Introduce `CtdMotdHoverMigration` to migrate legacy `sample-players-i…
WouterGritter Jun 18, 2026
3ed0f53
Guard against illegal configured values that would result in Arithmet…
WouterGritter Jun 18, 2026
b35aa77
Update defaults and clear motd-hover when the only entry is empty
WouterGritter Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ public interface ClusterPlayer {
*/
boolean isClientListingAllowed();

/**
* Gets the timestamp, in milliseconds since the epoch, at which the player connected
* to the proxy they are currently on.
*
* @return the join timestamp in milliseconds since the epoch
*/
long getJoinedAt();

/**
* Kicks the player from the proxy with the given reason.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public boolean isClientListingAllowed() {
return player.getPlayerSettings().isClientListingAllowed();
}

@Override
public long getJoinedAt() {
return player.getJoinedAt();
}

@Override
public void kick(Component reason) {
player.disconnect0(reason, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public boolean isClientListingAllowed() {
return redisEntry.isClientListingAllowed();
}

@Override
public long getJoinedAt() {
return redisEntry.getJoinedAt();
}

@Override
public void kick(Component reason) {
redis.publish(new VelocityKick(redisEntry.getUniqueId(), reason));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,33 @@ public class CtdConfigMigrations {

public static List<ConfigurationMigration> createCtdMigrations() {
return List.of(
new CtdMotdHoverMigration(),

// root
migration(
"Shown when hovering over the player count in the server list. Accepts a list of lines, and\n"
+ " supports the same placeholders as \"fallback-version-ping\" plus the \"{players}\" placeholder.\n"
+ "\n"
+ " \"{players}\" lists online players. By default it shows a random selection, which is the Notchian\n"
+ " (vanilla) behavior. Setting motd-hover = [\"{players}\"] reproduces the vanilla player tooltip.\n"
+ " It accepts optional arguments written as \"{players:arg=value:arg2=value}\":\n"
+ " - max: maximum number of players to list.\n"
+ " - maxPerLine: maximum number of players listed per line.\n"
+ " - ordering: how players are picked and ordered. One of:\n"
+ " - RANDOM: a random selection (Notchian behavior).\n"
+ " - ALPHABETICAL: sorted A-Z by name.\n"
+ " - ALPHABETICAL_REVERSED: sorted Z-A by name.\n"
+ " - LAST_JOINED: most recently joined players first.\n"
+ " - FIRST_JOINED: longest-connected players first.\n"
+ " - empty: text shown when no players are online.\n"
+ " - prefix: text inserted before each player name.\n"
+ " - separator: text inserted between player names (trimmed at the end of each line).\n"
+ " Example: \"{players:max=10:maxPerLine=2:ordering=ALPHABETICAL:separator=<gray>, }\"",
"motd-hover",
List.of(
"<gray>Players online: <white>{player-count}</white>/<white>{max-players}</white></gray>",
"{players}")
),
migration(
"Whether chat signing should be enforced. If disabled, backend servers MUST disable chat signing.",
"enforce-chat-signing",
Expand Down Expand Up @@ -147,10 +173,22 @@ public static List<ConfigurationMigration> createCtdMigrations() {
"advanced.server-brand",
"{backend-brand} ({proxy-brand})"
),
migration(
"Replaces what is returned for both the server brand and fallback version pinger.",
"advanced.custom-brand-proxy",
"Velocity-CTD"
),
migration(
"Replaces what is returned as the server brand for the user's client.",
"advanced.custom-brand-backend",
"Paper"
),
migration(
"Modifies the brand and server version that displays in the multiplayer menu and status pingers.\n"
+ " Supports placeholders: {protocol-min}, {protocol-max}, {protocol}, {proxy-brand},\n"
+ " {proxy-brand-custom}, {proxy-version}, {proxy-vendor}, {player-count}, {max-players}.",
+ " {proxy-brand-custom}, {proxy-version}, {proxy-vendor}, {player-count}, {max-players}.\n"
+ " These placeholders are also available in \"motd\" and \"motd-hover\", and the\n"
+ " \"{players}\" placeholder (documented under \"motd-hover\") may be used here as well.",
"advanced.fallback-version-ping",
"{proxy-brand} {protocol-min}-{protocol-max}"
),
Expand All @@ -162,14 +200,19 @@ public static List<ConfigurationMigration> createCtdMigrations() {
false
),
migration(
"Replaces what is returned for both the server brand and fallback version pinger.",
"advanced.custom-brand-proxy",
"Velocity-CTD"
"When a player disables \"Allow Server Listings\" in their client options, they are shown as\n"
+ " \"Anonymous Player\" in the {players} sample of the server list ping. Set this to true to ignore\n"
+ " that request and always show their real username.",
"advanced.ignore-anonymous-player-request",
false
),
migration(
"Replaces what is returned as the server brand for the user's client.",
"advanced.custom-brand-backend",
"Paper"
"Whether the {players} sample of the motd, motd-hover and fallback-version-ping should draw from a\n"
+ " single shared pool. When true, a player never appears more than once across those three sections.\n"
+ " When false (default), each section samples players independently and the same player may appear in\n"
+ " more than one section.",
"advanced.pool-players-across-sections",
false
),

// [redis]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2026 Velocity-CTD Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.velocityctd.proxy.config.migration;

import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.velocitypowered.proxy.config.migration.ConfigurationMigration;
import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.Logger;

/**
* Migrates the removed {@code sample-players-in-ping} option into the {@code motd-hover}
* {@code {players}} placeholder. When the option was enabled, the server list ping used to display
* a sample of online players, so a {@code {players}} line is inserted at the top of
* {@code motd-hover} to preserve that behavior. The legacy option is then removed.
*/
public final class CtdMotdHoverMigration implements ConfigurationMigration {

private static final String LEGACY_KEY = "sample-players-in-ping";
private static final String MOTD_HOVER_KEY = "motd-hover";
private static final String PLAYERS_LINE = "{players}";

@Override
public boolean shouldMigrate(CommentedFileConfig config) {
return config.contains(LEGACY_KEY);
}

@Override
public void migrate(CommentedFileConfig config, Logger logger) {
if (config.getOrElse(LEGACY_KEY, false)) {
List<String> motdHover = new ArrayList<>();
if (config.get(MOTD_HOVER_KEY) instanceof List<?> existing) {
for (Object line : existing) {
motdHover.add(String.valueOf(line));
}
}
motdHover.addFirst(PLAYERS_LINE);
config.set(MOTD_HOVER_KEY, motdHover);
}

config.remove(LEGACY_KEY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ public final class PlayerEntry extends DepotEntry<UUID, PlayerEntry> {
*/
private final String ipAddress;

/**
* The timestamp, in milliseconds since the epoch, at which the player joined the proxy.
*/
private final long joinedAt;

/**
* Whether this player entry may be listed in the server list ping MOTD hover, generated in
* {@link com.velocitypowered.proxy.connection.util.ServerListPingHandler}.
Expand Down Expand Up @@ -97,6 +102,7 @@ public final class PlayerEntry extends DepotEntry<UUID, PlayerEntry> {
.orElse(null);
this.ipAddress = player.getRemoteAddress().getAddress().getHostAddress();
this.clientListingAllowed = player.getPlayerSettings().isClientListingAllowed();
this.joinedAt = player.getJoinedAt();
}

/**
Expand Down Expand Up @@ -171,6 +177,15 @@ public void setServerName(String serverName) {
return ipAddress;
}

/**
* Gets the timestamp, in milliseconds since the epoch, at which the player joined the proxy.
*
* @return the join timestamp
*/
public long getJoinedAt() {
return joinedAt;
}

/**
* Whether this player entry may be listed in the server list ping MOTD hover, generated in
* {@link com.velocitypowered.proxy.connection.util.ServerListPingHandler}.
Expand Down
87 changes: 0 additions & 87 deletions proxy/src/main/java/com/velocityctd/proxy/util/ParsingUtils.java

This file was deleted.

Loading