7575import com .velocitypowered .proxy .config .ProxyAddress ;
7676import com .velocitypowered .proxy .config .VelocityConfiguration ;
7777import com .velocitypowered .proxy .connection .client .ConnectedPlayer ;
78+ import com .velocitypowered .proxy .connection .client .PlayerRegistry ;
7879import com .velocitypowered .proxy .connection .player .resourcepack .VelocityResourcePackInfo ;
7980import com .velocitypowered .proxy .connection .util .ServerListPingHandler ;
8081import com .velocitypowered .proxy .console .VelocityConsole ;
112113import java .util .Collections ;
113114import java .util .HashSet ;
114115import java .util .List ;
115- import java .util .Locale ;
116116import java .util .Map ;
117117import java .util .Objects ;
118118import java .util .Optional ;
119119import java .util .Set ;
120120import java .util .UUID ;
121121import java .util .concurrent .CompletableFuture ;
122- import java .util .concurrent .ConcurrentHashMap ;
123122import java .util .concurrent .CountDownLatch ;
124123import java .util .concurrent .ExecutionException ;
125124import java .util .concurrent .TimeUnit ;
@@ -204,9 +203,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
204203
205204 private final VelocityPluginManager pluginManager ;
206205
207- private final Map <UUID , ConnectedPlayer > connectionsByUuid = new ConcurrentHashMap <>();
208-
209- private final Map <String , ConnectedPlayer > connectionsByName = new ConcurrentHashMap <>();
206+ private final PlayerRegistry playerRegistry = new PlayerRegistry (this );
210207
211208 /**
212209 * Holds a set of all registered BuiltinCommand instances. Used for unregistering these commands later.
@@ -882,8 +879,10 @@ public void shutdown(boolean explicitExit, Component reason) {
882879 LOGGER .warn ("Interrupted while waiting for ProxyPreShutdownEvent; continuing shutdown." );
883880 }
884881
885- ImmutableList <@ NotNull ConnectedPlayer > players = ImmutableList .copyOf (connectionsByUuid .values ());
886- ImmutableList <@ NotNull UUID > playerUuids = ImmutableList .copyOf (connectionsByUuid .keySet ());
882+ ImmutableList <@ NotNull ConnectedPlayer > players = ImmutableList .copyOf (playerRegistry .getPlayers ());
883+ ImmutableList <@ NotNull UUID > playerUuids = players .stream ()
884+ .map (ConnectedPlayer ::getUniqueId )
885+ .collect (ImmutableList .toImmutableList ());
887886
888887 if (!getConfiguration ().isAcceptTransfers ()) {
889888 for (ConnectedPlayer player : players ) {
@@ -954,6 +953,8 @@ public void shutdown(boolean explicitExit, Component reason) {
954953 this .redis .shutdown ();
955954 }
956955
956+ this .playerRegistry .shutdown ();
957+
957958 // Since we manually removed the shutdown hook, we need to handle the shutdown ourselves.
958959 LogManager .shutdown ();
959960
@@ -1057,112 +1058,32 @@ public ConnectionManager getConnectionManager() {
10571058 return tabCompleteRateLimiter ;
10581059 }
10591060
1060- /**
1061- * Checks if a connection identified by the given profile can be registered with the proxy.
1062- * Called before the {@link ConnectedPlayer} instance is constructed so that rejected
1063- * connections never create a phantom player object.
1064- *
1065- * @param profile the resolved game profile of the incoming connection
1066- * @return {@code true} if we can register the connection, {@code false} if not
1067- */
1068- public boolean canRegisterConnection (GameProfile profile ) {
1069- // When kick-existing-players is enabled, skip duplicate checks here.
1070- // registerConnection() handles kicking the existing player and enforcing IP rules.
1071- if (configuration .isKickExistingPlayers ()) {
1072- return true ;
1073- }
1074-
1075- String lowerName = profile .getName ().toLowerCase (Locale .US );
1076-
1077- if (connectionsByName .containsKey (lowerName )) {
1078- return false ;
1079- }
1080-
1081- return !connectionsByUuid .containsKey (profile .getId ());
1061+ public PlayerRegistry getPlayerRegistry () {
1062+ return playerRegistry ;
10821063 }
10831064
10841065 /**
1085- * Attempts to register the {@code connection} with the proxy.
1066+ * Attempts to register the {@code connection} with the proxy, kicking any existing
1067+ * player under the same name or UUID if present and if
1068+ * {@link VelocityConfiguration#isKickExistingPlayers()} is {@code true}.
10861069 *
10871070 * @param connection the connection to register
1088- * @return {@code true} if we registered the connection, {@code false} if not
1071+ * @return a future resolving to {@code true} if we registered the connection, {@code false} if not
10891072 */
1090- public boolean registerConnection (ConnectedPlayer connection ) {
1091- String lowerName = connection .getUsername ().toLowerCase (Locale .US );
1092-
1093- if (!this .configuration .isKickExistingPlayers ()) {
1094- // Standard behavior: block duplicate connections
1095- if (connectionsByName .containsKey (lowerName )) {
1096- return false ;
1097- }
1098-
1099- connectionsByName .put (lowerName , connection );
1100-
1101- if (connectionsByUuid .putIfAbsent (connection .getUniqueId (), connection ) != null ) {
1102- connectionsByName .remove (lowerName , connection );
1103- return false ;
1104- }
1105-
1106- return true ;
1107- }
1108-
1109- // kick-existing-players is enabled. Kick the existing session so the new one can take over.
1110- // When kick-existing-players-check-ip is also enabled, only kick if the new connection comes
1111- // from the same IP address.
1112- ConnectedPlayer existingByUuid = connectionsByUuid .get (connection .getUniqueId ());
1113- if (existingByUuid != null ) {
1114- if (this .configuration .isKickExistingPlayersCheckIp ()) {
1115- InetAddress newIp = connection .getRemoteAddress ().getAddress ();
1116- InetAddress existingIp = existingByUuid .getRemoteAddress ().getAddress ();
1117- if (!newIp .equals (existingIp )) {
1118- // Different IP with same UUID: protect the existing player, deny the new connection.
1119- return false ;
1120- }
1121- }
1122- existingByUuid .disconnect (Component .translatable ("multiplayer.disconnect.duplicate_login" ));
1123- }
1124-
1125- // Also check for a username conflict whose UUID differs from the one above.
1126- ConnectedPlayer existingByName = connectionsByName .get (lowerName );
1127- if (existingByName != null && existingByName != existingByUuid ) {
1128- if (this .configuration .isKickExistingPlayersCheckIp ()) {
1129- InetAddress newIp = connection .getRemoteAddress ().getAddress ();
1130- InetAddress existingIp = existingByName .getRemoteAddress ().getAddress ();
1131- if (!newIp .equals (existingIp )) {
1132- return false ;
1133- }
1134- }
1135- existingByName .disconnect (Component .translatable ("multiplayer.disconnect.duplicate_login" ));
1136- }
1137-
1138- connectionsByName .put (lowerName , connection );
1139- connectionsByUuid .put (connection .getUniqueId (), connection );
1140-
1141- return true ;
1142- }
1143-
1144- /**
1145- * Unregisters the given player from the proxy.
1146- *
1147- * @param connection the connection to unregister
1148- */
1149- public void unregisterConnection (ConnectedPlayer connection ) {
1150- connectionsByName .remove (connection .getUsername ().toLowerCase (Locale .US ), connection );
1151- connectionsByUuid .remove (connection .getUniqueId (), connection );
1152- connection .disconnected ();
1073+ public CompletableFuture <Boolean > registerConnection (ConnectedPlayer connection ) {
1074+ return playerRegistry .registerConnection (connection );
11531075 }
11541076
11551077 @ Override
11561078 public Optional <ConnectedPlayer > getPlayer (String username ) {
11571079 Preconditions .checkNotNull (username , "username" );
1158-
1159- return Optional .ofNullable (connectionsByName .get (username .toLowerCase (Locale .US )));
1080+ return playerRegistry .getPlayer (username );
11601081 }
11611082
11621083 @ Override
11631084 public Optional <ConnectedPlayer > getPlayer (UUID uuid ) {
11641085 Preconditions .checkNotNull (uuid , "uuid" );
1165- return Optional . ofNullable ( connectionsByUuid . get ( uuid ) );
1086+ return playerRegistry . getPlayer ( uuid );
11661087 }
11671088
11681089 @ Override
@@ -1185,12 +1106,12 @@ public Collection<VelocityRegisteredServer> matchServer(String partialName) {
11851106
11861107 @ Override
11871108 public Collection <ConnectedPlayer > getAllPlayers () {
1188- return ImmutableList .copyOf (connectionsByUuid . values ());
1109+ return ImmutableList .copyOf (playerRegistry . getPlayers ());
11891110 }
11901111
11911112 @ Override
11921113 public @ UnmodifiableView Collection <ConnectedPlayer > getOnlinePlayers () {
1193- return Collections . unmodifiableCollection ( connectionsByUuid . values () );
1114+ return playerRegistry . getPlayers ( );
11941115 }
11951116
11961117 /**
@@ -1202,7 +1123,7 @@ public Collection<ConnectedPlayer> getAllPlayers() {
12021123 */
12031124 @ SuppressWarnings ("BooleanMethodIsAlwaysInverted" )
12041125 public boolean isPlayerOnline (ConnectedPlayer player ) {
1205- return connectionsByUuid . get (player . getUniqueId ()) == player ;
1126+ return playerRegistry . isPlayerOnline (player ) ;
12061127 }
12071128
12081129 @ Override
@@ -1218,7 +1139,7 @@ public int getPlayerCount() {
12181139 * @return the number of locally connected players
12191140 */
12201141 public int getLocalPlayerCount () {
1221- return connectionsByUuid . size ();
1142+ return playerRegistry . getLocalPlayerCount ();
12221143 }
12231144
12241145 @ Override
0 commit comments