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.bukkit.player; 020 021import com.google.common.base.Charsets; 022import com.plotsquared.bukkit.util.BukkitUtil; 023import com.plotsquared.core.PlotSquared; 024import com.plotsquared.core.configuration.Settings; 025import com.plotsquared.core.events.TeleportCause; 026import com.plotsquared.core.location.Location; 027import com.plotsquared.core.permissions.Permission; 028import com.plotsquared.core.permissions.PermissionHandler; 029import com.plotsquared.core.player.ConsolePlayer; 030import com.plotsquared.core.player.PlotPlayer; 031import com.plotsquared.core.plot.PlotWeather; 032import com.plotsquared.core.plot.world.PlotAreaManager; 033import com.plotsquared.core.util.EventDispatcher; 034import com.plotsquared.core.util.MathMan; 035import com.sk89q.worldedit.bukkit.BukkitAdapter; 036import com.sk89q.worldedit.extension.platform.Actor; 037import com.sk89q.worldedit.world.item.ItemType; 038import com.sk89q.worldedit.world.item.ItemTypes; 039import io.papermc.lib.PaperLib; 040import net.kyori.adventure.audience.Audience; 041import org.bukkit.GameMode; 042import org.bukkit.Sound; 043import org.bukkit.WeatherType; 044import org.bukkit.entity.Player; 045import org.bukkit.event.Event; 046import org.bukkit.event.EventException; 047import org.bukkit.event.player.PlayerTeleportEvent; 048import org.bukkit.permissions.PermissionAttachmentInfo; 049import org.bukkit.plugin.RegisteredListener; 050import org.bukkit.potion.PotionEffectType; 051import org.checkerframework.checker.index.qual.NonNegative; 052import org.checkerframework.checker.nullness.qual.NonNull; 053 054import java.util.Arrays; 055import java.util.Set; 056import java.util.UUID; 057 058import static com.sk89q.worldedit.world.gamemode.GameModes.ADVENTURE; 059import static com.sk89q.worldedit.world.gamemode.GameModes.CREATIVE; 060import static com.sk89q.worldedit.world.gamemode.GameModes.SPECTATOR; 061import static com.sk89q.worldedit.world.gamemode.GameModes.SURVIVAL; 062 063public class BukkitPlayer extends PlotPlayer<Player> { 064 065 private static boolean CHECK_EFFECTIVE = true; 066 public final Player player; 067 private String name; 068 069 /** 070 * @param plotAreaManager PlotAreaManager instance 071 * @param eventDispatcher EventDispatcher instance 072 * @param player Bukkit player instance 073 * @param permissionHandler PermissionHandler instance 074 * 075 * @deprecated Please do not use this method. Instead use {@link BukkitUtil#adapt(Player)}, as it caches player objects. 076 * This method will be made private in a future release. 077 */ 078 @Deprecated(forRemoval = true, since = "6.10.9") 079 public BukkitPlayer( 080 final @NonNull PlotAreaManager plotAreaManager, final @NonNull EventDispatcher eventDispatcher, 081 final @NonNull Player player, final @NonNull PermissionHandler permissionHandler 082 ) { 083 this(plotAreaManager, eventDispatcher, player, false, permissionHandler); 084 } 085 086 /** 087 * @param plotAreaManager PlotAreaManager instance 088 * @param eventDispatcher EventDispatcher instance 089 * @param player Bukkit player instance 090 * @param permissionHandler PermissionHandler instance 091 * 092 * @deprecated Please do not use this method. Instead use {@link BukkitUtil#adapt(Player)}, as it caches player objects. 093 * This method will be made private in a future release. 094 */ 095 @Deprecated(forRemoval = true, since = "6.10.9") 096 public BukkitPlayer( 097 final @NonNull PlotAreaManager plotAreaManager, final @NonNull 098 EventDispatcher eventDispatcher, final @NonNull Player player, 099 final boolean realPlayer, 100 final @NonNull PermissionHandler permissionHandler 101 ) { 102 super(plotAreaManager, eventDispatcher, permissionHandler); 103 this.player = player; 104 this.setupPermissionProfile(); 105 if (realPlayer) { 106 super.populatePersistentMetaMap(); 107 } 108 } 109 110 @Override 111 public Actor toActor() { 112 return BukkitAdapter.adapt(player); 113 } 114 115 @Override 116 public Player getPlatformPlayer() { 117 return this.player; 118 } 119 120 @NonNull 121 @Override 122 public UUID getUUID() { 123 if (Settings.UUID.OFFLINE) { 124 if (Settings.UUID.FORCE_LOWERCASE) { 125 return UUID.nameUUIDFromBytes(("OfflinePlayer:" + 126 getName().toLowerCase()).getBytes(Charsets.UTF_8)); 127 } else { 128 return UUID.nameUUIDFromBytes(("OfflinePlayer:" + 129 getName()).getBytes(Charsets.UTF_8)); 130 } 131 } 132 return player.getUniqueId(); 133 } 134 135 @Override 136 @NonNegative 137 public long getLastPlayed() { 138 return this.player.getLastSeen(); 139 } 140 141 @Override 142 public boolean canTeleport(final @NonNull Location location) { 143 final org.bukkit.Location to = BukkitUtil.adapt(location); 144 final org.bukkit.Location from = player.getLocation(); 145 PlayerTeleportEvent event = new PlayerTeleportEvent(player, from, to); 146 callEvent(event); 147 if (event.isCancelled() || !event.getTo().equals(to)) { 148 return false; 149 } 150 event = new PlayerTeleportEvent(player, to, from); 151 callEvent(event); 152 return true; 153 } 154 155 private void callEvent(final @NonNull Event event) { 156 final RegisteredListener[] listeners = event.getHandlers().getRegisteredListeners(); 157 for (final RegisteredListener listener : listeners) { 158 if (listener.getPlugin().getName().equals(PlotSquared.platform().pluginName())) { 159 continue; 160 } 161 try { 162 listener.callEvent(event); 163 } catch (final EventException e) { 164 e.printStackTrace(); 165 } 166 } 167 } 168 169 @SuppressWarnings("StringSplitter") 170 @Override 171 @NonNegative 172 public int hasPermissionRange( 173 final @NonNull String stub, 174 @NonNegative final int range 175 ) { 176 if (hasPermission(Permission.PERMISSION_ADMIN.toString())) { 177 return Integer.MAX_VALUE; 178 } 179 final String[] nodes = stub.split("\\."); 180 final StringBuilder n = new StringBuilder(); 181 for (int i = 0; i < (nodes.length - 1); i++) { 182 n.append(nodes[i]).append("."); 183 if (!stub.equals(n + Permission.PERMISSION_STAR.toString())) { 184 if (hasPermission(n + Permission.PERMISSION_STAR.toString())) { 185 return Integer.MAX_VALUE; 186 } 187 } 188 } 189 if (hasPermission(stub + ".*")) { 190 return Integer.MAX_VALUE; 191 } 192 int max = 0; 193 if (CHECK_EFFECTIVE) { 194 boolean hasAny = false; 195 String stubPlus = stub + "."; 196 final Set<PermissionAttachmentInfo> effective = player.getEffectivePermissions(); 197 if (!effective.isEmpty()) { 198 for (PermissionAttachmentInfo attach : effective) { 199 String permStr = attach.getPermission(); 200 if (permStr.startsWith(stubPlus)) { 201 hasAny = true; 202 String end = permStr.substring(stubPlus.length()); 203 if (MathMan.isInteger(end)) { 204 int val = Integer.parseInt(end); 205 if (val > range) { 206 return val; 207 } 208 if (val > max) { 209 max = val; 210 } 211 } 212 } 213 } 214 if (hasAny) { 215 return max; 216 } 217 // Workaround 218 for (PermissionAttachmentInfo attach : effective) { 219 String permStr = attach.getPermission(); 220 if (permStr.startsWith("plots.") && !permStr.equals("plots.use")) { 221 return max; 222 } 223 } 224 CHECK_EFFECTIVE = false; 225 } 226 } 227 for (int i = range; i > 0; i--) { 228 if (hasPermission(stub + "." + i)) { 229 return i; 230 } 231 } 232 return max; 233 } 234 235 @Override 236 public void teleport(final @NonNull Location location, final @NonNull TeleportCause cause) { 237 if (Math.abs(location.getX()) >= 30000000 || Math.abs(location.getZ()) >= 30000000) { 238 return; 239 } 240 final org.bukkit.Location bukkitLocation = 241 new org.bukkit.Location(BukkitUtil.getWorld(location.getWorldName()), location.getX() + 0.5, 242 location.getY(), location.getZ() + 0.5, location.getYaw(), location.getPitch() 243 ); 244 PaperLib.teleportAsync(player, bukkitLocation, getTeleportCause(cause)); 245 } 246 247 @Override 248 public String getName() { 249 if (this.name == null) { 250 this.name = this.player.getName(); 251 } 252 return this.name; 253 } 254 255 @Override 256 public void setCompassTarget(Location location) { 257 this.player.setCompassTarget( 258 new org.bukkit.Location(BukkitUtil.getWorld(location.getWorldName()), location.getX(), 259 location.getY(), location.getZ() 260 )); 261 } 262 263 @Override 264 public Location getLocationFull() { 265 return BukkitUtil.adaptComplete(this.player.getLocation()); 266 } 267 268 @Override 269 public void setWeather(final @NonNull PlotWeather weather) { 270 switch (weather) { 271 case CLEAR -> this.player.setPlayerWeather(WeatherType.CLEAR); 272 case RAIN -> this.player.setPlayerWeather(WeatherType.DOWNFALL); 273 case WORLD -> this.player.resetPlayerWeather(); 274 default -> { 275 //do nothing as this is PlotWeather.OFF 276 } 277 } 278 } 279 280 @Override 281 public com.sk89q.worldedit.world.gamemode.GameMode getGameMode() { 282 return switch (this.player.getGameMode()) { 283 case ADVENTURE -> ADVENTURE; 284 case CREATIVE -> CREATIVE; 285 case SPECTATOR -> SPECTATOR; 286 default -> SURVIVAL; 287 }; 288 } 289 290 @Override 291 public void setGameMode(final com.sk89q.worldedit.world.gamemode.GameMode gameMode) { 292 if (ADVENTURE.equals(gameMode)) { 293 this.player.setGameMode(GameMode.ADVENTURE); 294 } else if (CREATIVE.equals(gameMode)) { 295 this.player.setGameMode(GameMode.CREATIVE); 296 } else if (SPECTATOR.equals(gameMode)) { 297 this.player.setGameMode(GameMode.SPECTATOR); 298 } else { 299 this.player.setGameMode(GameMode.SURVIVAL); 300 } 301 } 302 303 @Override 304 public void setTime(final long time) { 305 if (time != Long.MAX_VALUE) { 306 this.player.setPlayerTime(time, false); 307 } else { 308 this.player.resetPlayerTime(); 309 } 310 } 311 312 @Override 313 public boolean getFlight() { 314 return player.getAllowFlight(); 315 } 316 317 @Override 318 public void setFlight(boolean fly) { 319 this.player.setAllowFlight(fly); 320 } 321 322 @Override 323 public void playMusic(final @NonNull Location location, final @NonNull ItemType id) { 324 if (id == ItemTypes.AIR) { 325 // Let's just stop all the discs because why not? 326 for (final Sound sound : Arrays.stream(Sound.values()) 327 .filter(sound -> sound.name().contains("DISC")).toList()) { 328 player.stopSound(sound); 329 } 330 // this.player.playEffect(BukkitUtil.getLocation(location), Effect.RECORD_PLAY, Material.AIR); 331 } else { 332 // this.player.playEffect(BukkitUtil.getLocation(location), Effect.RECORD_PLAY, id.to(Material.class)); 333 this.player.playSound(BukkitUtil.adapt(location), 334 Sound.valueOf(BukkitAdapter.adapt(id).name()), Float.MAX_VALUE, 1f 335 ); 336 } 337 } 338 339 @SuppressWarnings("deprecation") // Needed for Spigot compatibility 340 @Override 341 public void kick(final String message) { 342 this.player.kickPlayer(message); 343 } 344 345 @Override 346 public void stopSpectating() { 347 if (getGameMode() == SPECTATOR) { 348 this.player.setSpectatorTarget(null); 349 } 350 } 351 352 @Override 353 public boolean isBanned() { 354 return this.player.isBanned(); 355 } 356 357 @Override 358 public @NonNull Audience getAudience() { 359 return BukkitUtil.BUKKIT_AUDIENCES.player(this.player); 360 } 361 362 @Override 363 public void removeEffect(@NonNull String name) { 364 PotionEffectType type = PotionEffectType.getByName(name); 365 if (type != null) { 366 player.removePotionEffect(type); 367 } 368 } 369 370 @Override 371 public boolean canSee(final PlotPlayer<?> other) { 372 if (other instanceof ConsolePlayer) { 373 return true; 374 } else { 375 return this.player.canSee(((BukkitPlayer) other).getPlatformPlayer()); 376 } 377 } 378 379 /** 380 * Convert from PlotSquared's {@link TeleportCause} to Bukkit's {@link PlayerTeleportEvent.TeleportCause} 381 * 382 * @param cause PlotSquared teleport cause to convert 383 * @return Bukkit's equivalent teleport cause 384 */ 385 public PlayerTeleportEvent.TeleportCause getTeleportCause(final @NonNull TeleportCause cause) { 386 if (TeleportCause.CauseSets.COMMAND.contains(cause)) { 387 return PlayerTeleportEvent.TeleportCause.COMMAND; 388 } else if (cause == TeleportCause.UNKNOWN) { 389 return PlayerTeleportEvent.TeleportCause.UNKNOWN; 390 } 391 return PlayerTeleportEvent.TeleportCause.PLUGIN; 392 } 393 394}