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.ComponentLike; 034import net.kyori.adventure.text.TextComponent; 035import net.kyori.adventure.text.minimessage.MiniMessage; 036import net.kyori.adventure.text.minimessage.tag.Tag; 037import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 038import org.checkerframework.checker.nullness.qual.NonNull; 039import org.checkerframework.checker.nullness.qual.Nullable; 040 041import java.util.ArrayList; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.HashMap; 045import java.util.HashSet; 046import java.util.LinkedList; 047import java.util.List; 048import java.util.Map; 049import java.util.Set; 050import java.util.UUID; 051import java.util.concurrent.TimeUnit; 052import java.util.function.BiConsumer; 053 054/** 055 * Manages player instances 056 */ 057public abstract class PlayerManager<P extends PlotPlayer<? extends T>, T> { 058 059 private static final MiniMessage MINI_MESSAGE = MiniMessage.builder().build(); 060 061 private final Map<UUID, P> playerMap = new HashMap<>(); 062 private final Object playerLock = new Object(); 063 064 public static void getUUIDsFromString( 065 final @NonNull String list, 066 final @NonNull BiConsumer<Collection<UUID>, Throwable> consumer 067 ) { 068 String[] split = list.split(","); 069 070 final Set<UUID> result = new HashSet<>(); 071 final List<String> request = new LinkedList<>(); 072 073 for (final String name : split) { 074 if (name.isEmpty()) { 075 consumer.accept(Collections.emptySet(), null); 076 return; 077 } else if ("*".equals(name)) { 078 result.add(DBFunc.EVERYONE); 079 } else if (name.length() > 16) { 080 try { 081 result.add(UUID.fromString(name)); 082 } catch (IllegalArgumentException ignored) { 083 consumer.accept(Collections.emptySet(), null); 084 return; 085 } 086 } else { 087 request.add(name); 088 } 089 } 090 091 if (request.isEmpty()) { 092 consumer.accept(result, null); 093 } else { 094 PlotSquared.get().getImpromptuUUIDPipeline() 095 .getUUIDs(request, Settings.UUID.NON_BLOCKING_TIMEOUT) 096 .whenComplete((uuids, throwable) -> { 097 if (throwable != null) { 098 consumer.accept(null, throwable); 099 } else { 100 for (final UUIDMapping uuid : uuids) { 101 result.add(uuid.uuid()); 102 } 103 consumer.accept(result, null); 104 } 105 }); 106 } 107 } 108 109 /** 110 * Get a list of names given a list of UUIDs. 111 * - Uses the format {@link TranslatableCaption#of(String)} of "info.plot_user_list" for the returned string 112 * 113 * @param uuids UUIDs 114 * @param localeHolder the localeHolder to localize the component for 115 * @return Component of name list 116 */ 117 public static @NonNull Component getPlayerList(final @NonNull Collection<UUID> uuids, LocaleHolder localeHolder) { 118 if (uuids.isEmpty()) { 119 return TranslatableCaption.of("info.none").toComponent(localeHolder).asComponent(); 120 } 121 122 final List<UUID> players = new LinkedList<>(); 123 final List<ComponentLike> users = new LinkedList<>(); 124 for (final UUID uuid : uuids) { 125 if (uuid == null) { 126 users.add(TranslatableCaption.of("info.none").toComponent(localeHolder)); 127 } else if (DBFunc.EVERYONE.equals(uuid)) { 128 users.add(TranslatableCaption.of("info.everyone").toComponent(localeHolder)); 129 } else if (DBFunc.SERVER.equals(uuid)) { 130 users.add(TranslatableCaption.of("info.console").toComponent(localeHolder)); 131 } else { 132 players.add(uuid); 133 } 134 } 135 136 try { 137 for (final UUIDMapping mapping : PlotSquared.get().getImpromptuUUIDPipeline() 138 .getNames(players).get(Settings.UUID.BLOCKING_TIMEOUT, TimeUnit.MILLISECONDS)) { 139 users.add(Component.text(mapping.username())); 140 } 141 } catch (final Exception e) { 142 e.printStackTrace(); 143 } 144 145 String c = TranslatableCaption.of("info.plot_user_list").getComponent(ConsolePlayer.getConsole()); 146 TextComponent.Builder list = Component.text(); 147 for (int x = 0; x < users.size(); x++) { 148 if (x + 1 == uuids.size()) { 149 list.append(MINI_MESSAGE.deserialize(c, TagResolver.resolver( 150 "user", 151 Tag.inserting(users.get(x)) 152 ))); 153 } else { 154 list.append(MINI_MESSAGE.deserialize(c + ", ", TagResolver.resolver( 155 "user", 156 Tag.inserting(users.get(x)) 157 ))); 158 } 159 } 160 return list.asComponent(); 161 } 162 163 /** 164 * Attempts to resolve the username by an uuid 165 * <p> 166 * <b>Note:</b> blocks the thread until the name was resolved or failed 167 * 168 * @param owner The UUID of the owner 169 * @return A caption containing either the name, {@code None}, {@code Everyone} or {@code Unknown} 170 * @see #resolveName(UUID, boolean) 171 * @since 6.4.0 172 */ 173 public static @NonNull Caption resolveName(final @Nullable UUID owner) { 174 return resolveName(owner, true); 175 } 176 177 /** 178 * Attempts to resolve the username by an uuid 179 * 180 * @param owner The UUID of the owner 181 * @param blocking If the operation should block the current thread for {@link Settings.UUID#BLOCKING_TIMEOUT} milliseconds 182 * @return A caption containing either the name, {@code None}, {@code Everyone} or {@code Unknown} 183 * @since 6.4.0 184 */ 185 public static @NonNull Caption resolveName(final @Nullable UUID owner, final boolean blocking) { 186 if (owner == null) { 187 return TranslatableCaption.of("info.none"); 188 } 189 if (owner.equals(DBFunc.EVERYONE)) { 190 return TranslatableCaption.of("info.everyone"); 191 } 192 if (owner.equals(DBFunc.SERVER)) { 193 return TranslatableCaption.of("info.server"); 194 } 195 final String name; 196 if (blocking) { 197 name = PlotSquared.get().getImpromptuUUIDPipeline() 198 .getSingle(owner, Settings.UUID.BLOCKING_TIMEOUT); 199 } else { 200 final UUIDMapping uuidMapping = 201 PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(owner); 202 if (uuidMapping != null) { 203 name = uuidMapping.username(); 204 } else { 205 name = null; 206 } 207 } 208 if (name == null) { 209 return TranslatableCaption.of("info.unknown"); 210 } 211 return StaticCaption.of(name); 212 } 213 214 /** 215 * Remove a player from the player map 216 * 217 * @param plotPlayer Player to remove 218 */ 219 public void removePlayer(final @NonNull PlotPlayer<?> plotPlayer) { 220 synchronized (playerLock) { 221 this.playerMap.remove(plotPlayer.getUUID()); 222 } 223 } 224 225 /** 226 * Remove a player from the player map 227 * 228 * @param uuid Player to remove 229 */ 230 public void removePlayer(final @NonNull UUID uuid) { 231 synchronized (playerLock) { 232 this.playerMap.remove(uuid); 233 } 234 } 235 236 /** 237 * Get the player from its UUID if it is stored in the player map. 238 * 239 * @param uuid Player UUID 240 * @return Player, or null 241 */ 242 public @Nullable P getPlayerIfExists(final @Nullable UUID uuid) { 243 if (uuid == null) { 244 return null; 245 } 246 return this.playerMap.get(uuid); 247 } 248 249 public @Nullable P getPlayerIfExists(final @Nullable String name) { 250 for (final P plotPlayer : this.playerMap.values()) { 251 if (plotPlayer.getName().equalsIgnoreCase(name)) { 252 return plotPlayer; 253 } 254 } 255 return null; 256 } 257 258 /** 259 * Get a plot player from a platform player object. This method requires 260 * that the caller actually knows that the player exists and is online. 261 * <p> 262 * The method will throw an exception if there is no such 263 * player online. 264 * 265 * @param object Platform player object 266 * @return Player object 267 */ 268 public @NonNull 269 abstract P getPlayer(final @NonNull T object); 270 271 /** 272 * Get a plot player from a UUID. This method requires 273 * that the caller actually knows that the player exists. 274 * <p> 275 * The method will throw an exception if there is no such 276 * player online. 277 * 278 * @param uuid Player UUID 279 * @return Player object 280 */ 281 public @NonNull P getPlayer(final @NonNull UUID uuid) { 282 synchronized (playerLock) { 283 P player = this.playerMap.get(uuid); 284 if (player == null) { 285 player = createPlayer(uuid); 286 this.playerMap.put(uuid, player); 287 } 288 return player; 289 } 290 } 291 292 public @NonNull 293 abstract P createPlayer(final @NonNull UUID uuid); 294 295 /** 296 * Get an an offline player object from the player's UUID 297 * 298 * @param uuid Player UUID 299 * @return Offline player object 300 */ 301 public @Nullable 302 abstract OfflinePlotPlayer getOfflinePlayer(final @Nullable UUID uuid); 303 304 /** 305 * Get an offline player object from the player's username 306 * 307 * @param username Player name 308 * @return Offline player object 309 */ 310 public @Nullable 311 abstract OfflinePlotPlayer getOfflinePlayer(final @NonNull String username); 312 313 /** 314 * Get all online players 315 * 316 * @return Unmodifiable collection of players 317 */ 318 public Collection<P> getPlayers() { 319 return Collections.unmodifiableCollection(new ArrayList<>(this.playerMap.values())); 320 } 321 322 323 public static final class NoSuchPlayerException extends IllegalArgumentException { 324 325 public NoSuchPlayerException(final @NonNull UUID uuid) { 326 super(String.format("There is no online player with UUID '%s'", uuid)); 327 } 328 329 } 330 331}