001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core.util; 020 021import com.plotsquared.core.PlotSquared; 022import com.plotsquared.core.configuration.Settings; 023import com.plotsquared.core.configuration.caption.Caption; 024import com.plotsquared.core.configuration.caption.LocaleHolder; 025import com.plotsquared.core.configuration.caption.StaticCaption; 026import com.plotsquared.core.configuration.caption.TranslatableCaption; 027import com.plotsquared.core.database.DBFunc; 028import com.plotsquared.core.player.ConsolePlayer; 029import com.plotsquared.core.player.OfflinePlotPlayer; 030import com.plotsquared.core.player.PlotPlayer; 031import com.plotsquared.core.uuid.UUIDMapping; 032import net.kyori.adventure.text.Component; 033import net.kyori.adventure.text.TextComponent; 034import net.kyori.adventure.text.minimessage.MiniMessage; 035import net.kyori.adventure.text.minimessage.Template; 036import org.checkerframework.checker.nullness.qual.NonNull; 037import org.checkerframework.checker.nullness.qual.Nullable; 038 039import java.util.ArrayList; 040import java.util.Collection; 041import java.util.Collections; 042import java.util.HashMap; 043import java.util.HashSet; 044import java.util.LinkedList; 045import java.util.List; 046import java.util.Map; 047import java.util.Set; 048import java.util.UUID; 049import java.util.concurrent.TimeUnit; 050import java.util.function.BiConsumer; 051 052/** 053 * Manages player instances 054 */ 055public abstract class PlayerManager<P extends PlotPlayer<? extends T>, T> { 056 057 private static final MiniMessage MINI_MESSAGE = MiniMessage.builder().build(); 058 059 private final Map<UUID, P> playerMap = new HashMap<>(); 060 private final Object playerLock = new Object(); 061 062 public static void getUUIDsFromString( 063 final @NonNull String list, 064 final @NonNull BiConsumer<Collection<UUID>, Throwable> consumer 065 ) { 066 String[] split = list.split(","); 067 068 final Set<UUID> result = new HashSet<>(); 069 final List<String> request = new LinkedList<>(); 070 071 for (final String name : split) { 072 if (name.isEmpty()) { 073 consumer.accept(Collections.emptySet(), null); 074 return; 075 } else if ("*".equals(name)) { 076 result.add(DBFunc.EVERYONE); 077 } else if (name.length() > 16) { 078 try { 079 result.add(UUID.fromString(name)); 080 } catch (IllegalArgumentException ignored) { 081 consumer.accept(Collections.emptySet(), null); 082 return; 083 } 084 } else { 085 request.add(name); 086 } 087 } 088 089 if (request.isEmpty()) { 090 consumer.accept(result, null); 091 } else { 092 PlotSquared.get().getImpromptuUUIDPipeline() 093 .getUUIDs(request, Settings.UUID.NON_BLOCKING_TIMEOUT) 094 .whenComplete((uuids, throwable) -> { 095 if (throwable != null) { 096 consumer.accept(null, throwable); 097 } else { 098 for (final UUIDMapping uuid : uuids) { 099 result.add(uuid.getUuid()); 100 } 101 consumer.accept(result, null); 102 } 103 }); 104 } 105 } 106 107 /** 108 * Get a list of names given a list of UUIDs. 109 * - Uses the format {@link TranslatableCaption#of(String)} of "info.plot_user_list" for the returned string 110 * 111 * @param uuids UUIDs 112 * @param localeHolder the localeHolder to localize the component for 113 * @return Component of name list 114 */ 115 public static @NonNull Component getPlayerList(final @NonNull Collection<UUID> uuids, LocaleHolder localeHolder) { 116 if (uuids.isEmpty()) { 117 return MINI_MESSAGE.parse(TranslatableCaption.of("info.none").getComponent(localeHolder)); 118 } 119 120 final List<UUID> players = new LinkedList<>(); 121 final List<String> users = new LinkedList<>(); 122 for (final UUID uuid : uuids) { 123 if (uuid == null) { 124 users.add(MINI_MESSAGE.stripTokens(TranslatableCaption.of("info.none").getComponent(localeHolder))); 125 } else if (DBFunc.EVERYONE.equals(uuid)) { 126 users.add(MINI_MESSAGE.stripTokens(TranslatableCaption.of("info.everyone").getComponent(localeHolder))); 127 } else if (DBFunc.SERVER.equals(uuid)) { 128 users.add(MINI_MESSAGE.stripTokens(TranslatableCaption.of("info.console").getComponent(localeHolder))); 129 } else { 130 players.add(uuid); 131 } 132 } 133 134 try { 135 for (final UUIDMapping mapping : PlotSquared.get().getImpromptuUUIDPipeline() 136 .getNames(players).get(Settings.UUID.BLOCKING_TIMEOUT, TimeUnit.MILLISECONDS)) { 137 users.add(mapping.getUsername()); 138 } 139 } catch (final Exception e) { 140 e.printStackTrace(); 141 } 142 143 String c = TranslatableCaption.of("info.plot_user_list").getComponent(ConsolePlayer.getConsole()); 144 TextComponent.Builder list = Component.text(); 145 for (int x = 0; x < users.size(); x++) { 146 if (x + 1 == uuids.size()) { 147 list.append(MINI_MESSAGE.parse(c, Template.of("user", users.get(x)))); 148 } else { 149 list.append(MINI_MESSAGE.parse(c + ", ", Template.of("user", users.get(x)))); 150 } 151 } 152 return list.asComponent(); 153 } 154 155 /** 156 * Get the name from a UUID. 157 * 158 * @param owner Owner UUID 159 * @return The player's name, None, Everyone or Unknown 160 * @deprecated Use {@link #resolveName(UUID)} 161 */ 162 @Deprecated(forRemoval = true, since = "6.4.0") 163 public static @NonNull String getName(final @Nullable UUID owner) { 164 return getName(owner, true); 165 } 166 167 /** 168 * Get the name from a UUID. 169 * 170 * @param owner Owner UUID 171 * @param blocking Whether or not the operation can be blocking 172 * @return The player's name, None, Everyone or Unknown 173 * @deprecated Use {@link #resolveName(UUID, boolean)} 174 */ 175 @Deprecated(forRemoval = true, since = "6.4.0") 176 public static @NonNull String getName(final @Nullable UUID owner, final boolean blocking) { 177 if (owner == null) { 178 TranslatableCaption.of("info.none"); 179 } 180 if (owner.equals(DBFunc.EVERYONE)) { 181 TranslatableCaption.of("info.everyone"); 182 } 183 if (owner.equals(DBFunc.SERVER)) { 184 TranslatableCaption.of("info.server"); 185 } 186 final String name; 187 if (blocking) { 188 name = PlotSquared.get().getImpromptuUUIDPipeline() 189 .getSingle(owner, Settings.UUID.BLOCKING_TIMEOUT); 190 } else { 191 final UUIDMapping uuidMapping = 192 PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(owner); 193 if (uuidMapping != null) { 194 name = uuidMapping.getUsername(); 195 } else { 196 name = null; 197 } 198 } 199 if (name == null) { 200 TranslatableCaption.of("info.unknown"); 201 } 202 return name; 203 } 204 205 /** 206 * Attempts to resolve the username by an uuid 207 * <p> 208 * <b>Note:</b> blocks the thread until the name was resolved or failed 209 * 210 * @param owner The UUID of the owner 211 * @return A caption containing either the name, {@code None}, {@code Everyone} or {@code Unknown} 212 * @see #resolveName(UUID, boolean) 213 * @since 6.4.0 214 */ 215 public static @NonNull Caption resolveName(final @Nullable UUID owner) { 216 return resolveName(owner, true); 217 } 218 219 /** 220 * Attempts to resolve the username by an uuid 221 * 222 * @param owner The UUID of the owner 223 * @param blocking If the operation should block the current thread for {@link Settings.UUID#BLOCKING_TIMEOUT} milliseconds 224 * @return A caption containing either the name, {@code None}, {@code Everyone} or {@code Unknown} 225 * @since 6.4.0 226 */ 227 public static @NonNull Caption resolveName(final @Nullable UUID owner, final boolean blocking) { 228 if (owner == null) { 229 return TranslatableCaption.of("info.none"); 230 } 231 if (owner.equals(DBFunc.EVERYONE)) { 232 return TranslatableCaption.of("info.everyone"); 233 } 234 if (owner.equals(DBFunc.SERVER)) { 235 return TranslatableCaption.of("info.server"); 236 } 237 final String name; 238 if (blocking) { 239 name = PlotSquared.get().getImpromptuUUIDPipeline() 240 .getSingle(owner, Settings.UUID.BLOCKING_TIMEOUT); 241 } else { 242 final UUIDMapping uuidMapping = 243 PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(owner); 244 if (uuidMapping != null) { 245 name = uuidMapping.getUsername(); 246 } else { 247 name = null; 248 } 249 } 250 if (name == null) { 251 return TranslatableCaption.of("info.unknown"); 252 } 253 return StaticCaption.of(name); 254 } 255 256 /** 257 * Remove a player from the player map 258 * 259 * @param plotPlayer Player to remove 260 */ 261 public void removePlayer(final @NonNull PlotPlayer<?> plotPlayer) { 262 synchronized (playerLock) { 263 this.playerMap.remove(plotPlayer.getUUID()); 264 } 265 } 266 267 /** 268 * Remove a player from the player map 269 * 270 * @param uuid Player to remove 271 */ 272 public void removePlayer(final @NonNull UUID uuid) { 273 synchronized (playerLock) { 274 this.playerMap.remove(uuid); 275 } 276 } 277 278 /** 279 * Get the player from its UUID if it is stored in the player map. 280 * 281 * @param uuid Player UUID 282 * @return Player, or null 283 */ 284 public @Nullable P getPlayerIfExists(final @Nullable UUID uuid) { 285 if (uuid == null) { 286 return null; 287 } 288 return this.playerMap.get(uuid); 289 } 290 291 public @Nullable P getPlayerIfExists(final @Nullable String name) { 292 for (final P plotPlayer : this.playerMap.values()) { 293 if (plotPlayer.getName().equalsIgnoreCase(name)) { 294 return plotPlayer; 295 } 296 } 297 return null; 298 } 299 300 /** 301 * Get a plot player from a platform player object. This method requires 302 * that the caller actually knows that the player exists and is online. 303 * <p> 304 * The method will throw an exception if there is no such 305 * player online. 306 * 307 * @param object Platform player object 308 * @return Player object 309 */ 310 public @NonNull 311 abstract P getPlayer(final @NonNull T object); 312 313 /** 314 * Get a plot player from a UUID. This method requires 315 * that the caller actually knows that the player exists. 316 * <p> 317 * The method will throw an exception if there is no such 318 * player online. 319 * 320 * @param uuid Player UUID 321 * @return Player object 322 */ 323 public @NonNull P getPlayer(final @NonNull UUID uuid) { 324 synchronized (playerLock) { 325 P player = this.playerMap.get(uuid); 326 if (player == null) { 327 player = createPlayer(uuid); 328 this.playerMap.put(uuid, player); 329 } 330 return player; 331 } 332 } 333 334 public @NonNull 335 abstract P createPlayer(final @NonNull UUID uuid); 336 337 /** 338 * Get an an offline player object from the player's UUID 339 * 340 * @param uuid Player UUID 341 * @return Offline player object 342 */ 343 public @Nullable 344 abstract OfflinePlotPlayer getOfflinePlayer(final @Nullable UUID uuid); 345 346 /** 347 * Get an offline player object from the player's username 348 * 349 * @param username Player name 350 * @return Offline player object 351 */ 352 public @Nullable 353 abstract OfflinePlotPlayer getOfflinePlayer(final @NonNull String username); 354 355 /** 356 * Get all online players 357 * 358 * @return Unmodifiable collection of players 359 */ 360 public Collection<P> getPlayers() { 361 return Collections.unmodifiableCollection(new ArrayList<>(this.playerMap.values())); 362 } 363 364 365 public static final class NoSuchPlayerException extends IllegalArgumentException { 366 367 public NoSuchPlayerException(final @NonNull UUID uuid) { 368 super(String.format("There is no online player with UUID '%s'", uuid)); 369 } 370 371 } 372 373}